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

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

commit 1d6186fface82890ef72cfcaa8279f87f96ca0d8
Author: Tian Jiang <[email protected]>
AuthorDate: Wed Sep 16 15:11:10 2026 +0800

    tmp
---
 .../it/session/IoTDBTableModelSessionIT.java       | 214 +++++++++++++++++++++
 .../org/apache/iotdb/isession/ITableSession.java   |  15 ++
 .../apache/iotdb/session/i18n/SessionMessages.java |   4 +
 .../apache/iotdb/session/i18n/SessionMessages.java |   5 +
 .../apache/iotdb/session/SessionConnection.java    |  42 ++++
 .../org/apache/iotdb/session/TableSession.java     |  12 +-
 .../apache/iotdb/db/i18n/DataNodeMiscMessages.java |   2 +
 .../apache/iotdb/db/i18n/DataNodeMiscMessages.java |   4 +
 .../iotdb/db/protocol/session/IClientSession.java  |  53 +++++
 .../iotdb/db/protocol/session/SessionManager.java  |   2 +
 .../protocol/thrift/impl/ClientRPCServiceImpl.java |  65 +++++++
 11 files changed, 417 insertions(+), 1 deletion(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBTableModelSessionIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBTableModelSessionIT.java
index f9c7ca00444..828ddba1fbd 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBTableModelSessionIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBTableModelSessionIT.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.relational.it.session;
 
 import org.apache.iotdb.isession.ITableSession;
 import org.apache.iotdb.isession.NumericRow;
+import org.apache.iotdb.isession.NumericRowPreparedStatement;
 import org.apache.iotdb.isession.SessionDataSet;
 import org.apache.iotdb.isession.pool.ITableSessionPool;
 import org.apache.iotdb.it.env.EnvFactory;
@@ -33,16 +34,26 @@ import org.apache.iotdb.rpc.StatementExecutionException;
 import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.read.common.RowRecord;
+import org.apache.tsfile.write.record.Tablet;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
 
 import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.IntConsumer;
 
 import static 
org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.showTablesColumnHeaders;
 import static org.junit.Assert.assertEquals;
@@ -182,6 +193,209 @@ public class IoTDBTableModelSessionIT {
     }
   }
 
+  @Test
+  public void testPreparedNumericRowAndTabletWritePerformance() throws 
Exception {
+    Assume.assumeTrue(
+        "Performance benchmark disabled; set 
iotdb.numeric.row.insert.perf.enabled=true to run",
+        Boolean.getBoolean("iotdb.numeric.row.insert.perf.enabled"));
+    final int concurrency = 16;
+    // Compare sustained single-row writes after warming both paths; verify 
all acknowledged rows.
+    final int warmupRounds = 2;
+    final int measuredRounds = 6;
+    final int fieldCount = 99;
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("CREATE DATABASE numeric_perf");
+      session.executeNonQueryStatement("USE numeric_perf");
+      StringBuilder ddl = new StringBuilder("CREATE TABLE prepared_perf (tag 
STRING TAG");
+      for (int i = 0; i < fieldCount; i++) {
+        ddl.append(", f").append(i).append(" DOUBLE FIELD");
+      }
+      ddl.append(")");
+      session.executeNonQueryStatement(ddl.toString());
+      session.executeNonQueryStatement(ddl.toString().replace("prepared_perf", 
"tablet_perf"));
+    }
+
+    List<String> names = new ArrayList<>();
+    List<TSDataType> types = new ArrayList<>();
+    List<ColumnCategory> categories = new ArrayList<>();
+    names.add("tag");
+    types.add(TSDataType.STRING);
+    categories.add(ColumnCategory.TAG);
+    for (int i = 0; i < fieldCount; i++) {
+      names.add("f" + i);
+      types.add(TSDataType.DOUBLE);
+      categories.add(ColumnCategory.FIELD);
+    }
+    ITableSession[] sessions = new ITableSession[concurrency];
+    NumericRowPreparedStatement[] prepared = new 
NumericRowPreparedStatement[concurrency];
+    Tablet[] tablets = new Tablet[concurrency];
+    String[][] tags = new String[concurrency][];
+    long[][] numericValues = new long[concurrency][fieldCount];
+    long[] preparedRows = new long[concurrency];
+    long[] tabletRows = new long[concurrency];
+    ExecutorService executor = Executors.newFixedThreadPool(concurrency);
+    try {
+      // Connections, USE, prepare and reusable input allocation are outside 
every timed round.
+      for (int i = 0; i < concurrency; i++) {
+        sessions[i] = EnvFactory.getEnv().getTableSessionConnection();
+        sessions[i].executeNonQueryStatement("USE numeric_perf");
+        prepared[i] =
+            sessions[i].prepareInsertNumericRow("prepared_perf", names, types, 
categories);
+        tablets[i] = new Tablet("tablet_perf", names, types, categories, 1);
+        tags[i] = new String[] {"device-" + i};
+        Arrays.fill(numericValues[i], Double.doubleToRawLongBits(1.0));
+      }
+      IntConsumer preparedWrite =
+          threadId -> {
+            try {
+              sessions[threadId].executePreparedInsertNumericRow(
+                  prepared[threadId],
+                  preparedRows[threadId],
+                  numericValues[threadId],
+                  tags[threadId]);
+              preparedRows[threadId]++;
+            } catch (Exception e) {
+              throw new RuntimeException(e);
+            }
+          };
+      IntConsumer tabletWrite =
+          threadId -> {
+            try {
+              Tablet tablet = tablets[threadId];
+              tablet.addTimestamp(0, tabletRows[threadId]);
+              tablet.addValue("tag", 0, tags[threadId][0]);
+              for (int i = 0; i < fieldCount; i++) {
+                tablet.addValue(names.get(i + 1), 0, 1.0D);
+              }
+              sessions[threadId].insert(tablet);
+              tablet.reset();
+              tabletRows[threadId]++;
+            } catch (Exception e) {
+              throw new RuntimeException(e);
+            }
+          };
+      double[][] rates = new double[2][measuredRounds];
+      long[] totalRows = new long[2];
+      long[] totalNanos = new long[2];
+      for (int round = 0; round < warmupRounds + measuredRounds; round++) {
+        boolean warmup = round < warmupRounds;
+        int seconds = warmup ? 3 : 5;
+        // Alternate AB/BA to distribute shared JVM warmup and background 
storage work.
+        for (int position = 0; position < 2; position++) {
+          int path = (round + position) % 2;
+          WritePerformance result =
+              runConcurrentWrites(
+                  executor, concurrency, seconds, path == 0 ? preparedWrite : 
tabletWrite);
+          System.out.printf(
+              "%s round %d position %d %s: %d rows, %.3f s, %.1f rows/s%n",
+              warmup ? "warmup" : "measured",
+              warmup ? round + 1 : round - warmupRounds + 1,
+              position + 1,
+              path == 0 ? "prepared numeric row" : "insertTablet batch=1",
+              result.rows(),
+              result.nanos() / 1_000_000_000.0,
+              result.rowsPerSecond());
+          if (!warmup) {
+            rates[path][round - warmupRounds] = result.rowsPerSecond();
+            totalRows[path] += result.rows();
+            totalNanos[path] += result.nanos();
+          }
+        }
+      }
+      for (int path = 0; path < 2; path++) {
+        Arrays.sort(rates[path]);
+        System.out.printf(
+            "summary %s: median %.1f rows/s, aggregate %.1f rows/s, min %.1f, 
max %.1f%n",
+            path == 0 ? "prepared numeric row" : "insertTablet batch=1",
+            (rates[path][measuredRounds / 2 - 1] + rates[path][measuredRounds 
/ 2]) / 2,
+            totalRows[path] * 1_000_000_000.0 / totalNanos[path],
+            rates[path][0],
+            rates[path][measuredRounds - 1]);
+      }
+      // Per-device monotonic timestamps span warmup and measurement, so no 
rows overwrite others.
+      for (int path = 0; path < 2; path++) {
+        String table = path == 0 ? "prepared_perf" : "tablet_perf";
+        long expectedRows = Arrays.stream(path == 0 ? preparedRows : 
tabletRows).sum();
+        try (SessionDataSet dataSet =
+            sessions[0].executeQueryStatement("SELECT count(*) FROM " + 
table)) {
+          assertTrue(dataSet.hasNext());
+          assertEquals(expectedRows, 
dataSet.next().getFields().get(0).getLongV());
+          assertFalse(dataSet.hasNext());
+        }
+        System.out.printf("verified %s: %d rows%n", table, expectedRows);
+      }
+    } finally {
+      executor.shutdownNow();
+      try {
+        assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
+      } finally {
+        // Closing each session also releases its server-side prepared 
metadata.
+        Exception closeFailure = null;
+        for (ITableSession session : sessions) {
+          if (session != null) {
+            try {
+              session.close();
+            } catch (Exception e) {
+              if (closeFailure == null) closeFailure = e;
+              else closeFailure.addSuppressed(e);
+            }
+          }
+        }
+        if (closeFailure != null) throw closeFailure;
+      }
+    }
+  }
+
+  private record WritePerformance(long rows, long nanos) {
+    double rowsPerSecond() {
+      return rows * 1_000_000_000.0 / nanos;
+    }
+  }
+
+  private static WritePerformance runConcurrentWrites(
+      ExecutorService executor, int concurrency, int seconds, IntConsumer 
write) throws Exception {
+    CountDownLatch ready = new CountDownLatch(concurrency);
+    CountDownLatch start = new CountDownLatch(1);
+    AtomicLong deadline = new AtomicLong();
+    List<Future<WritePerformance>> futures = new ArrayList<>();
+    try {
+      for (int i = 0; i < concurrency; i++) {
+        final int threadId = i;
+        futures.add(
+            executor.submit(
+                () -> {
+                  ready.countDown();
+                  start.await();
+                  long rows = 0;
+                  while (!Thread.currentThread().isInterrupted()
+                      && System.nanoTime() < deadline.get()) {
+                    write.accept(threadId);
+                    rows++;
+                  }
+                  return new WritePerformance(rows, System.nanoTime());
+                }));
+      }
+      assertTrue(ready.await(30, TimeUnit.SECONDS));
+      long startNanos = System.nanoTime();
+      deadline.set(startNanos + TimeUnit.SECONDS.toNanos(seconds));
+      start.countDown();
+      long rows = 0;
+      long endNanos = startNanos;
+      for (Future<WritePerformance> future : futures) {
+        WritePerformance result = future.get(seconds + 60, TimeUnit.SECONDS);
+        rows += result.rows();
+        endNanos = Math.max(endNanos, result.nanos());
+      }
+      // Include completion of in-flight RPCs, but exclude main-thread 
collection/printing time.
+      return new WritePerformance(rows, endNanos - startNanos);
+    } finally {
+      start.countDown();
+      for (Future<WritePerformance> future : futures) {
+        if (!future.isDone()) future.cancel(true);
+      }
+    }
+  }
+
   private void assertNumericTagRows(ITableSession session) throws Exception {
     String[] devices = {"设备甲😀", "设备乙", "", null};
     try (SessionDataSet dataSet =
diff --git 
a/iotdb-client/isession/src/main/java/org/apache/iotdb/isession/ITableSession.java
 
b/iotdb-client/isession/src/main/java/org/apache/iotdb/isession/ITableSession.java
index af524f9a853..d14abab1238 100644
--- 
a/iotdb-client/isession/src/main/java/org/apache/iotdb/isession/ITableSession.java
+++ 
b/iotdb-client/isession/src/main/java/org/apache/iotdb/isession/ITableSession.java
@@ -22,8 +22,12 @@ package org.apache.iotdb.isession;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.StatementExecutionException;
 
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.write.record.Tablet;
 
+import java.util.List;
+
 /**
  * This interface defines a session for interacting with IoTDB tables. It 
supports operations such
  * as data insertion, executing queries, and closing the session. 
Implementations of this interface
@@ -58,6 +62,17 @@ public interface ITableSession extends AutoCloseable {
    */
   void insert(NumericRow row) throws StatementExecutionException, 
IoTDBConnectionException;
 
+  NumericRowPreparedStatement prepareInsertNumericRow(
+      String tableName,
+      List<String> columnNames,
+      List<TSDataType> dataTypes,
+      List<ColumnCategory> columnCategories)
+      throws StatementExecutionException, IoTDBConnectionException;
+
+  void executePreparedInsertNumericRow(
+      NumericRowPreparedStatement prepared, long timestamp, long[] values, 
String[] tagValues)
+      throws StatementExecutionException, IoTDBConnectionException;
+
   /**
    * Executes a non-query SQL statement, such as a DDL or DML command.
    *
diff --git 
a/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
 
b/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
index 3eb19971a92..173260e4a07 100644
--- 
a/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
+++ 
b/iotdb-client/session/src/main/i18n/en/org/apache/iotdb/session/i18n/SessionMessages.java
@@ -290,4 +290,8 @@ public final class SessionMessages {
 
   public static final String 
EXCEPTION_NUMERIC_ROW_ARGUMENTS_MUST_BE_NON_NULL_AND_COLUMN_NAMES_TYPES_CATEGORIES_AND_VALUES_MUST_HAVE_EQUAL_LENGTHS_AB102B01
 = "Numeric row arguments must be non-null and column names, types, categories 
and values must have equal lengths.";
   public static final String 
EXCEPTION_NUMERIC_ROWS_SUPPORT_ONLY_FIELD_COLUMNS_DA7A8EE7 = "Numeric rows 
support only FIELD columns.";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_BELONGS_TO_ANOTHER_CONNECTION_PREPARE_IT_AGAIN_055CC7BC
 = "Prepared numeric row is closed or belongs to another connection; prepare it 
again.";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_PREPARE_RETURNED_NO_STATUS_6FF7BC24 = "Prepared 
numeric row prepare returned no status";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_VALUE_COUNT_MISMATCH_9ACD3A0C = "Prepared 
numeric row value count mismatch";
+  public static final String EXCEPTION_UNSUPPORTED_NUMERIC_TYPE_06DD3044 = 
"Unsupported numeric type";
 }
diff --git 
a/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
 
b/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
index 22f3bea7821..b45704d8738 100644
--- 
a/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
+++ 
b/iotdb-client/session/src/main/i18n/zh/org/apache/iotdb/session/i18n/SessionMessages.java
@@ -288,4 +288,9 @@ public final class SessionMessages {
 
   public static final String 
EXCEPTION_NUMERIC_ROW_ARGUMENTS_MUST_BE_NON_NULL_AND_COLUMN_NAMES_TYPES_CATEGORIES_AND_VALUES_MUST_HAVE_EQUAL_LENGTHS_AB102B01
 = "数值行参数不能为 null,列名、类型、类别和值的数量必须相同。";
   public static final String 
EXCEPTION_NUMERIC_ROWS_SUPPORT_ONLY_FIELD_COLUMNS_DA7A8EE7 = "数值行仅支持 FIELD 列。";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_BELONGS_TO_ANOTHER_CONNECTION_PREPARE_IT_AGAIN_055CC7BC
 =
+      "预备数值行已关闭或属于其他连接,请重新准备";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_PREPARE_RETURNED_NO_STATUS_6FF7BC24 = 
"预备数值行准备请求未返回状态";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_VALUE_COUNT_MISMATCH_9ACD3A0C = "预备数值行的值数量不匹配";
+  public static final String EXCEPTION_UNSUPPORTED_NUMERIC_TYPE_06DD3044 = 
"不支持的数值类型";
 }
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 2e144448b58..e01c5dd8a20 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
@@ -44,6 +44,7 @@ import 
org.apache.iotdb.service.rpc.thrift.TSCreateSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSCreateTimeseriesReq;
 import org.apache.iotdb.service.rpc.thrift.TSDeleteDataReq;
 import org.apache.iotdb.service.rpc.thrift.TSDropSchemaTemplateReq;
+import org.apache.iotdb.service.rpc.thrift.TSExecutePreparedNumericRowReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp;
 import org.apache.iotdb.service.rpc.thrift.TSFastLastDataQueryForOneDeviceReq;
@@ -59,6 +60,8 @@ import org.apache.iotdb.service.rpc.thrift.TSInsertTabletsReq;
 import org.apache.iotdb.service.rpc.thrift.TSLastDataQueryReq;
 import org.apache.iotdb.service.rpc.thrift.TSOpenSessionReq;
 import org.apache.iotdb.service.rpc.thrift.TSOpenSessionResp;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowReq;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowResp;
 import org.apache.iotdb.service.rpc.thrift.TSPruneSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSQueryTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSQueryTemplateResp;
@@ -79,6 +82,7 @@ import org.apache.tsfile.utils.Preconditions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.nio.ByteBuffer;
 import java.security.SecureRandom;
 import java.time.ZoneId;
 import java.util.ArrayList;
@@ -759,6 +763,44 @@ public class SessionConnection {
     callWithRetryAndVerifyWithRedirection(() -> insertRecordInternal(request));
   }
 
+  protected long prepareNumericRow(TSPrepareNumericRowReq request)
+      throws IoTDBConnectionException, StatementExecutionException {
+    request.setSessionId(sessionId);
+    try {
+      TSPrepareNumericRowResp resp = client.prepareNumericRow(request);
+      if (resp.getStatus() == null) {
+        throw new IoTDBConnectionException(
+            
SessionMessages.EXCEPTION_PREPARED_NUMERIC_ROW_PREPARE_RETURNED_NO_STATUS_6FF7BC24);
+      }
+      RpcUtils.verifySuccess(resp.getStatus());
+      return resp.getPreparedId();
+    } catch (TException e) {
+      throw new IoTDBConnectionException(e);
+    }
+  }
+
+  protected void executePreparedNumericRow(
+      long id, long timestamp, ByteBuffer values, ByteBuffer tags)
+      throws IoTDBConnectionException, StatementExecutionException {
+    TSExecutePreparedNumericRowReq request =
+        new TSExecutePreparedNumericRowReq(sessionId, id, timestamp, values, 
tags);
+    try {
+      RpcUtils.verifySuccess(client.executePreparedNumericRow(request));
+    } catch (TException e) {
+      throw new IoTDBConnectionException(e);
+    }
+  }
+
+  protected void closePreparedNumericRow(long id) throws 
IoTDBConnectionException {
+    try {
+      RpcUtils.verifySuccess(client.closePreparedNumericRow(sessionId, id));
+    } catch (StatementExecutionException e) {
+      throw new IoTDBConnectionException(e);
+    } catch (TException e) {
+      throw new IoTDBConnectionException(e);
+    }
+  }
+
   private TSStatus insertRecordInternal(TSInsertRecordReq request) throws 
TException {
     request.setSessionId(sessionId);
     return client.insertRecord(request);
diff --git 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
index 38cc6d72cea..f7154b3c5f7 100644
--- 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
+++ 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
@@ -21,17 +21,27 @@ package org.apache.iotdb.session;
 
 import org.apache.iotdb.isession.ITableSession;
 import org.apache.iotdb.isession.NumericRow;
+import org.apache.iotdb.isession.NumericRowPreparedStatement;
 import org.apache.iotdb.isession.SessionDataSet;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowReq;
+import org.apache.iotdb.session.i18n.SessionMessages;
 
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
 import org.apache.tsfile.write.record.Tablet;
 
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
 public class TableSession implements ITableSession {
 
   private final Session session;
 
-  TableSession(Session session) {
+  public TableSession(Session session) {
     this.session = session;
   }
 
diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index 12f4e374ca7..d4ce7f97ba8 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -1489,4 +1489,6 @@ public final class DataNodeMiscMessages {
       
LOG_TABLE_QUERY_DEVICE_ENTRY_BATCH_SIZE_IN_BYTES_ARG_EXCEEDS_DN_THRIFT_MAX_FRAME_SIZE_ARG_USING_ARG_AS_THE_EFFECTIVE_VALUE_2AE1BEDA
 =
           "table_query_device_entry_batch_size_in_bytes (%d) exceeds the 
maximum RPC payload (dn_thrift_max_frame_size %d minus 1024 bytes); using %d as 
the effective value";
 
+  public static final String 
EXCEPTION_INVALID_PREPARED_NUMERIC_ROW_METADATA_OR_PAYLOAD_AECCCEAA = "Invalid 
prepared numeric row metadata or payload.";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_DOES_NOT_BELONG_TO_THIS_SESSION_5CC5C50A
 = "Prepared numeric row is closed or does not belong to this session.";
 }
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
index d809fea1992..2d19c74be8b 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeMiscMessages.java
@@ -1469,4 +1469,8 @@ public final class DataNodeMiscMessages {
       
LOG_TABLE_QUERY_DEVICE_ENTRY_BATCH_SIZE_IN_BYTES_ARG_EXCEEDS_DN_THRIFT_MAX_FRAME_SIZE_ARG_USING_ARG_AS_THE_EFFECTIVE_VALUE_2AE1BEDA
 =
           "table_query_device_entry_batch_size_in_bytes(%d)超过最大 RPC 
payload(dn_thrift_max_frame_size %d 减去 1024 字节),将使用 %d 作为生效值";
 
+  public static final String 
EXCEPTION_INVALID_PREPARED_NUMERIC_ROW_METADATA_OR_PAYLOAD_AECCCEAA =
+      "预备数值行元数据或数据载荷无效";
+  public static final String 
EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_DOES_NOT_BELONG_TO_THIS_SESSION_5CC5C50A
 =
+      "预备数值行已关闭或不属于当前会话";
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/IClientSession.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/IClientSession.java
index bac4b15e342..0283b226bdf 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/IClientSession.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/IClientSession.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.commons.queryengine.common.SqlDialect;
 import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
 import org.apache.iotdb.commons.utils.PathUtils;
 import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
+import 
org.apache.iotdb.db.queryengine.plan.execution.config.session.PreparedStatementMemoryManager;
 import org.apache.iotdb.rpc.subscription.annotation.TableModel;
 import org.apache.iotdb.service.rpc.thrift.TSConnectionInfo;
 import org.apache.iotdb.service.rpc.thrift.TSConnectionType;
@@ -33,12 +34,64 @@ import org.apache.iotdb.service.rpc.thrift.TSConnectionType;
 import javax.annotation.Nullable;
 
 import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicLong;
 
 public abstract class IClientSession {
 
+  private static final AtomicLong NUMERIC_ROW_IDS = new AtomicLong();
+  private Map<Long, PreparedNumericRowInfo> preparedNumericRows;
+  private boolean numericRowsClosed;
+
+  public synchronized long addPreparedNumericRow(PreparedNumericRowInfo info) {
+    if (numericRowsClosed) {
+      throw new SemanticException(
+          DataNodeMiscMessages
+              
.EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_DOES_NOT_BELONG_TO_THIS_SESSION_5CC5C50A);
+    }
+    long preparedId = NUMERIC_ROW_IDS.incrementAndGet();
+    if (preparedNumericRows == null) {
+      preparedNumericRows = new HashMap<>();
+    }
+    PreparedStatementMemoryManager.getInstance()
+        .allocate(Long.toString(preparedId), info.ramBytesUsed());
+    preparedNumericRows.put(preparedId, info);
+    return preparedId;
+  }
+
+  public synchronized PreparedNumericRowInfo getPreparedNumericRow(long 
preparedId) {
+    PreparedNumericRowInfo info =
+        preparedNumericRows == null ? null : 
preparedNumericRows.get(preparedId);
+    if (info == null) {
+      throw new SemanticException(
+          DataNodeMiscMessages
+              
.EXCEPTION_PREPARED_NUMERIC_ROW_IS_CLOSED_OR_DOES_NOT_BELONG_TO_THIS_SESSION_5CC5C50A);
+    }
+    return info;
+  }
+
+  public synchronized void removePreparedNumericRow(long preparedId) {
+    PreparedNumericRowInfo info =
+        preparedNumericRows == null ? null : 
preparedNumericRows.remove(preparedId);
+    if (info != null) {
+      
PreparedStatementMemoryManager.getInstance().release(info.ramBytesUsed());
+    }
+  }
+
+  public synchronized void releasePreparedNumericRows() {
+    numericRowsClosed = true;
+    if (preparedNumericRows != null) {
+      for (PreparedNumericRowInfo info : preparedNumericRows.values()) {
+        
PreparedStatementMemoryManager.getInstance().release(info.ramBytesUsed());
+      }
+      preparedNumericRows = null;
+    }
+  }
+
   private long id;
 
   private ClientVersion clientVersion;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
index 21dbffb606e..fa1da5d8ea5 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
@@ -260,6 +260,8 @@ public class SessionManager implements SessionManagerMBean {
       }
     }
 
+    session.releasePreparedNumericRows();
+
     // Release PreparedStatement memory resources
     try {
       
PreparedStatementMemoryManager.getInstance().releaseAllForSession(session);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
index 99d40b3572b..d48aef4cbf6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
@@ -69,6 +69,7 @@ import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
 import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
 import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
 import org.apache.iotdb.db.protocol.session.IClientSession;
+import org.apache.iotdb.db.protocol.session.PreparedNumericRowInfo;
 import org.apache.iotdb.db.protocol.session.SessionManager;
 import org.apache.iotdb.db.protocol.thrift.OperationType;
 import org.apache.iotdb.db.queryengine.common.header.DatasetHeader;
@@ -170,6 +171,7 @@ import 
org.apache.iotdb.service.rpc.thrift.TSDeallocatePreparedReq;
 import org.apache.iotdb.service.rpc.thrift.TSDeleteDataReq;
 import org.apache.iotdb.service.rpc.thrift.TSDropSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteBatchStatementReq;
+import org.apache.iotdb.service.rpc.thrift.TSExecutePreparedNumericRowReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecutePreparedReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementReq;
 import org.apache.iotdb.service.rpc.thrift.TSExecuteStatementResp;
@@ -191,6 +193,8 @@ import 
org.apache.iotdb.service.rpc.thrift.TSInsertTabletsReq;
 import org.apache.iotdb.service.rpc.thrift.TSLastDataQueryReq;
 import org.apache.iotdb.service.rpc.thrift.TSOpenSessionReq;
 import org.apache.iotdb.service.rpc.thrift.TSOpenSessionResp;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowReq;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowResp;
 import org.apache.iotdb.service.rpc.thrift.TSPrepareReq;
 import org.apache.iotdb.service.rpc.thrift.TSPrepareResp;
 import org.apache.iotdb.service.rpc.thrift.TSProtocolVersion;
@@ -2582,6 +2586,67 @@ public class ClientRPCServiceImpl implements 
IClientRPCServiceWithHandler {
     }
   }
 
+  @Override
+  public TSPrepareNumericRowResp prepareNumericRow(TSPrepareNumericRowReq req) 
{
+    IClientSession session = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime();
+    if (!SESSION_MANAGER.checkLogin(session)) {
+      return new TSPrepareNumericRowResp(getNotLoggedInStatus());
+    }
+    try {
+      if (req.getSessionId() != session.getId()) {
+        return new 
TSPrepareNumericRowResp(RpcUtils.getStatus(TSStatusCode.NOT_LOGIN));
+      }
+      PreparedNumericRowInfo info = new 
PreparedNumericRowInfo(session.getDatabaseName(), req);
+      TSPrepareNumericRowResp response = new TSPrepareNumericRowResp();
+      response.setStatus(RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS));
+      response.setPreparedId(session.addPreparedNumericRow(info));
+      return response;
+    } catch (Exception e) {
+      return new TSPrepareNumericRowResp(
+          onQueryException(
+              e, OperationType.INSERT_RECORD.getName(), 
TSStatusCode.EXECUTE_STATEMENT_ERROR));
+    }
+  }
+
+  @Override
+  public TSStatus executePreparedNumericRow(TSExecutePreparedNumericRowReq 
req) {
+    IClientSession session = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime();
+    if (!SESSION_MANAGER.checkLogin(session)) return getNotLoggedInStatus();
+    try {
+      if (req.getSessionId() != session.getId()) return 
RpcUtils.getStatus(TSStatusCode.NOT_LOGIN);
+      InsertBaseStatement statement =
+          session
+              .getPreparedNumericRow(req.getPreparedId())
+              .bind(
+                  req.getTimestamp(),
+                  ByteBuffer.wrap(req.getTagValues()),
+                  ByteBuffer.wrap(req.getValues()));
+      long queryId = SESSION_MANAGER.requestQueryId();
+      return COORDINATOR.executeForTableModel(
+              statement,
+              relationSqlParser,
+              session,
+              queryId,
+              SESSION_MANAGER.getSessionInfo(session),
+              "",
+              metadata,
+              config.getConnectionTimeoutInMS())
+          .status;
+    } catch (Exception e) {
+      return onQueryException(
+          e, OperationType.INSERT_RECORD.getName(), 
TSStatusCode.EXECUTE_STATEMENT_ERROR);
+    }
+  }
+
+  @Override
+  public TSStatus closePreparedNumericRow(long sessionId, long preparedId) {
+    IClientSession session = SESSION_MANAGER.getCurrSessionAndUpdateIdleTime();
+    if (!SESSION_MANAGER.checkLogin(session) || session.getId() != sessionId)
+      return getNotLoggedInStatus();
+    session.removePreparedNumericRow(preparedId);
+    return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS);
+  }
+
   @Override
   public TSStatus insertRecord(TSInsertRecordReq req) {
     long t1 = System.nanoTime();

Reply via email to