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 028ceb4341f3b2e13e8d94e5eb60ac712cdea9a4
Author: Tian Jiang <[email protected]>
AuthorDate: Thu Sep 17 10:33:50 2026 +0800

    Add schema check bypass
---
 .../it/session/IoTDBTableModelSessionIT.java       |  39 ++++
 .../protocol/session/PreparedNumericRowInfo.java   |  59 ++++++
 .../plan/relational/sql/ast/InsertNumericRow.java  |  29 +++
 .../statement/crud/InsertNumericRowStatement.java  |  11 +
 .../session/PreparedNumericRowInfoTest.java        | 225 +++++++++++++++++++++
 .../apache/iotdb/commons/schema/table/TsTable.java |  10 +
 6 files changed, 373 insertions(+)

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 828ddba1fbd..29ee26b30c0 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
@@ -193,6 +193,45 @@ public class IoTDBTableModelSessionIT {
     }
   }
 
+  @Test
+  public void testPreparedNumericRowSchemaVersionChange() throws Exception {
+    // Reuse a prepared row before and after adding a TAG, including a 
flush/read round trip.
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("CREATE DATABASE numeric_version");
+      session.executeNonQueryStatement("USE numeric_version");
+      session.executeNonQueryStatement("CREATE TABLE readings (tag STRING TAG, 
v DOUBLE FIELD)");
+      NumericRowPreparedStatement prepared =
+          session.prepareInsertNumericRow(
+              "readings",
+              Arrays.asList("tag", "v"),
+              Arrays.asList(TSDataType.STRING, TSDataType.DOUBLE),
+              Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD));
+      for (int time = 1; time <= 3; time++) {
+        if (time == 3) {
+          session.executeNonQueryStatement("ALTER TABLE readings ADD COLUMN 
tag2 STRING TAG");
+        }
+        session.executePreparedInsertNumericRow(
+            prepared,
+            time,
+            new long[] {Double.doubleToRawLongBits(time + 0.5)},
+            new String[] {"device"});
+      }
+      session.executeNonQueryStatement("FLUSH");
+      try (SessionDataSet dataSet =
+          session.executeQueryStatement("SELECT time,tag,tag2,v FROM readings 
ORDER BY time")) {
+        for (int time = 1; time <= 3; time++) {
+          assertTrue(dataSet.hasNext());
+          RowRecord row = dataSet.next();
+          assertEquals(time, row.getFields().get(0).getLongV());
+          assertEquals("device", row.getFields().get(1).getStringValue());
+          assertNull(row.getFields().get(2).getDataType());
+          assertEquals(time + 0.5, row.getFields().get(3).getDoubleV(), 0);
+        }
+        assertFalse(dataSet.hasNext());
+      }
+    }
+  }
+
   @Test
   public void testPreparedNumericRowAndTabletWritePerformance() throws 
Exception {
     Assume.assumeTrue(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfo.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfo.java
index a1a67e45100..6d5dc1fce4b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfo.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfo.java
@@ -22,16 +22,24 @@ package org.apache.iotdb.db.protocol.session;
 import org.apache.iotdb.commons.exception.SemanticException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils;
+import org.apache.iotdb.commons.schema.table.TsTable;
 import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
 import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
+import org.apache.iotdb.db.pipe.resource.memory.InsertNodeMemoryEstimator;
 import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertNumericRowStatement;
+import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
+import org.apache.iotdb.db.schemaengine.table.DataNodeTreeViewSchemaUtils;
 import org.apache.iotdb.db.utils.NumericRowValues;
 import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowReq;
 
+import org.apache.tsfile.common.conf.TSFileDescriptor;
 import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.Pair;
 import org.apache.tsfile.utils.RamUsageEstimator;
 import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.apache.tsfile.write.schema.MeasurementSchema;
 
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
@@ -39,6 +47,8 @@ import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
 
+import static 
org.apache.iotdb.db.utils.EncodingInferenceUtils.getDefaultEncoding;
+
 /**
  * Session-owned metadata. Each execution gets fresh arrays because schema 
analysis mutates them.
  */
@@ -50,6 +60,8 @@ public final class PreparedNumericRowInfo {
   private final TsTableColumnCategory[] categories;
   private final int tagCount;
   private final int valueBytes;
+  private final Pair<Long, Long> tableVersion;
+  private final MeasurementSchema[] measurementSchemas;
 
   public PreparedNumericRowInfo(String database, TSPrepareNumericRowReq req) {
     if (req == null) {
@@ -152,6 +164,44 @@ public final class PreparedNumericRowInfo {
       throw invalid(
           
DataNodeMiscMessages.EXCEPTION_PREPARED_NUMERIC_ROW_HAS_NO_FIELD_COLUMNS_A76209AE);
     }
+    TsTable currentTable = 
DataNodeTableCache.getInstance().getTableInWrite(database, table);
+    Pair<Long, Long> version = currentTable == null ? null : 
currentTable.getInstanceVersion();
+    MeasurementSchema[] schemas = prepareMeasurementSchemas(currentTable);
+    // Never retain a snapshot assembled across concurrent schema revisions.
+    if (schemas != null && currentTable.matchesInstanceVersion(version)) {
+      tableVersion = version;
+      measurementSchemas = schemas;
+    } else {
+      tableVersion = null;
+      measurementSchemas = null;
+    }
+  }
+
+  private MeasurementSchema[] prepareMeasurementSchemas(TsTable currentTable) {
+    // Views and requests needing TAG padding/reordering or type conversion 
use normal analysis.
+    if (currentTable == null
+        || currentTable.getClass() != TsTable.class
+        || DataNodeTreeViewSchemaUtils.isTreeViewTable(currentTable)
+        || currentTable.getTagColumnSchemaList().size() != tagCount) {
+      return null;
+    }
+    MeasurementSchema[] schemas = new MeasurementSchema[measurements.length];
+    for (int i = 0; i < measurements.length; i++) {
+      TsTableColumnSchema column = 
currentTable.getColumnSchema(measurements[i]);
+      if (column == null
+          || column.getDataType() != types[i]
+          || column.getColumnCategory() != categories[i]
+          || (i < tagCount && 
currentTable.getTagColumnOrdinal(measurements[i]) != i)) {
+        return null;
+      }
+      schemas[i] =
+          new MeasurementSchema(
+              column.getColumnName(),
+              types[i],
+              getDefaultEncoding(types[i]),
+              
TSFileDescriptor.getInstance().getConfig().getCompressor(types[i]));
+    }
+    return schemas;
   }
 
   public InsertNumericRowStatement bind(long timestamp, ByteBuffer tags, 
ByteBuffer values) {
@@ -217,6 +267,10 @@ public final class PreparedNumericRowInfo {
     statement.setColumnCategories(categories.clone());
     statement.setTime(timestamp);
     statement.setNumericValues(row);
+    if (measurementSchemas != null) {
+      statement.setPreparedTableVersion(tableVersion);
+      statement.setMeasurementSchemas(measurementSchemas.clone());
+    }
     return statement;
   }
 
@@ -227,6 +281,11 @@ public final class PreparedNumericRowInfo {
         + RamUsageEstimator.sizeOf(measurements)
         + RamUsageEstimator.shallowSizeOf(types)
         + RamUsageEstimator.shallowSizeOf(categories)
+        + 
InsertNodeMemoryEstimator.sizeOfMeasurementSchemas(measurementSchemas)
+        + (tableVersion == null
+            ? 0
+            : RamUsageEstimator.shallowSizeOf(tableVersion)
+                + 2 * RamUsageEstimator.shallowSizeOfInstance(Long.class))
         // Account for the map entry and boxed ID retained by the session.
         + 64;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertNumericRow.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertNumericRow.java
index 38f29f01d2c..4510973f753 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertNumericRow.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertNumericRow.java
@@ -20,8 +20,13 @@
 package org.apache.iotdb.db.queryengine.plan.relational.sql.ast;
 
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IAstVisitor;
+import org.apache.iotdb.commons.schema.table.TsTable;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import 
org.apache.iotdb.db.queryengine.plan.analyze.lock.DataNodeSchemaLockManager;
+import org.apache.iotdb.db.queryengine.plan.analyze.lock.SchemaLockType;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertNumericRowStatement;
+import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -43,6 +48,30 @@ public class InsertNumericRow extends WrappedInsertStatement 
{
     return (InsertNumericRowStatement) super.getInnerTreeStatement();
   }
 
+  @Override
+  public void validateTableSchema(Metadata metadata, MPPQueryContext context) {
+    InsertNumericRowStatement statement = getInnerTreeStatement();
+    if (statement.getPreparedTableVersion() != null) {
+      // Check at analysis time under the same deletion lock and metadata 
lease as normal inserts.
+      DataNodeSchemaLockManager.getInstance()
+          .takeReadLock(context, SchemaLockType.VALIDATE_VS_DELETION_TABLE);
+      TsTable currentTable =
+          DataNodeTableCache.getInstance().getTableInWrite(getDatabase(), 
getTableName());
+      if (currentTable != null
+          && 
currentTable.matchesInstanceVersion(statement.getPreparedTableVersion())) {
+        statement.setAttributeColumnsPresent(false);
+        // Cached schema names must not be mutated by later normalization.
+        statement.setToLowerCaseApplied(true);
+        statement.semanticCheck();
+        return;
+      }
+      // Full validation may cast, reorder, or fail columns; discard all 
cached schema state first.
+      statement.setPreparedTableVersion(null);
+      statement.setMeasurementSchemas(null);
+    }
+    super.validateTableSchema(metadata, context);
+  }
+
   @Override
   public void updateAfterSchemaValidation(MPPQueryContext context) {
     // Numeric rows are already typed; schema validation casts values in their 
primitive storage.
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertNumericRowStatement.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertNumericRowStatement.java
index fe77996fabe..7093b1615fb 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertNumericRowStatement.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertNumericRowStatement.java
@@ -39,6 +39,7 @@ import org.apache.iotdb.db.utils.NumericRowValues;
 import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.utils.Pair;
 import org.apache.tsfile.utils.RamUsageEstimator;
 import org.apache.tsfile.write.UnSupportedDataTypeException;
 
@@ -55,6 +56,8 @@ public class InsertNumericRowStatement extends 
InsertBaseStatement {
   private long time;
   private NumericRowValues numericValues;
   private IDeviceID deviceID;
+  // A candidate snapshot, checked again under the schema analysis lock before 
reuse.
+  private Pair<Long, Long> preparedTableVersion;
 
   public InsertNumericRowStatement() {
     statementType = StatementType.INSERT_NUMERIC_ROW;
@@ -62,6 +65,14 @@ public class InsertNumericRowStatement extends 
InsertBaseStatement {
     isAligned = true;
   }
 
+  public Pair<Long, Long> getPreparedTableVersion() {
+    return preparedTableVersion;
+  }
+
+  public void setPreparedTableVersion(Pair<Long, Long> preparedTableVersion) {
+    this.preparedTableVersion = preparedTableVersion;
+  }
+
   public long getTime() {
     return time;
   }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfoTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfoTest.java
new file mode 100644
index 00000000000..e195d69660c
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/session/PreparedNumericRowInfoTest.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.protocol.session;
+
+import org.apache.iotdb.commons.queryengine.common.SessionInfo;
+import org.apache.iotdb.commons.schema.table.TreeViewSchema;
+import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema;
+import org.apache.iotdb.commons.schema.table.column.TagColumnSchema;
+import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import org.apache.iotdb.db.queryengine.common.QueryId;
+import 
org.apache.iotdb.db.queryengine.plan.analyze.lock.DataNodeSchemaLockManager;
+import org.apache.iotdb.db.queryengine.plan.analyze.lock.SchemaLockType;
+import org.apache.iotdb.db.queryengine.plan.analyze.schema.SchemaValidator;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableHeaderSchemaValidator;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertNumericRow;
+import 
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertNumericRowStatement;
+import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
+import org.apache.iotdb.service.rpc.thrift.TSPrepareNumericRowReq;
+
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.time.ZoneId;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class PreparedNumericRowInfoTest {
+  private static final String DATABASE = "prepared_numeric_fastpath_test";
+  private TsTable table;
+  private MPPQueryContext context;
+  private Metadata metadata;
+
+  @Before
+  public void setUp() {
+    DataNodeTableCache.getInstance().invalid(DATABASE);
+    table = new TsTable("t");
+    table.addColumnSchema(new TagColumnSchema("tag", TSDataType.STRING));
+    table.addColumnSchema(new FieldColumnSchema("v", TSDataType.DOUBLE));
+    install(table);
+    context = new MPPQueryContext(new QueryId("prepared_test"));
+    context.setSession(new SessionInfo(1, "root", ZoneId.of("UTC")));
+    metadata = mock(Metadata.class);
+    doAnswer(
+            invocation -> {
+              TableHeaderSchemaValidator.getInstance()
+                  .validateInsertNodeMeasurements(
+                      invocation.getArgument(0),
+                      invocation.getArgument(1),
+                      invocation.getArgument(2),
+                      invocation.getArgument(3),
+                      invocation.getArgument(4),
+                      invocation.getArgument(5));
+              return null;
+            })
+        .when(metadata)
+        .validateInsertNodeMeasurements(anyString(), any(), any(), 
anyBoolean(), any(), any());
+  }
+
+  @After
+  public void tearDown() {
+    DataNodeSchemaLockManager.getInstance().releaseReadLock(context);
+    DataNodeTableCache.getInstance().invalid(DATABASE);
+  }
+
+  // An unchanged table skips column validation but still checks access and 
device metadata.
+  @Test
+  public void testUnchangedVersionRetainsOtherChecks() {
+    PreparedNumericRowInfo info = new PreparedNumericRowInfo(DATABASE, 
request("tag", "v"));
+    InsertNumericRowStatement statement = bind(info, 1);
+    InsertNumericRowStatement next = bind(info, 1);
+    assertNotNull(statement.getPreparedTableVersion());
+    assertNotSame(statement.getMeasurementSchemas(), 
next.getMeasurementSchemas());
+    assertSame(statement.getMeasurementSchemas()[1], 
next.getMeasurementSchemas()[1]);
+    AccessControl accessControl = mock(AccessControl.class);
+    InsertNumericRow insert = new InsertNumericRow(statement, context);
+    SchemaValidator.validate(metadata, insert, context, accessControl);
+    verify(metadata, never())
+        .validateInsertNodeMeasurements(anyString(), any(), any(), 
anyBoolean(), any(), any());
+    verify(metadata).validateDeviceSchema(insert, context);
+    verify(accessControl).checkCanInsertIntoTable(eq("root"), any(), 
eq(context));
+    
assertTrue(context.getAcquiredLocks().contains(SchemaLockType.VALIDATE_VS_DELETION_TABLE));
+    assertEquals(TSDataType.DOUBLE, 
statement.getMeasurementSchemas()[1].getType());
+    statement.getMeasurementSchemas()[1] = null;
+    assertNotNull(bind(info, 1).getMeasurementSchemas()[1]);
+  }
+
+  // Adding a TAG after bind forces normal validation to pad the new device-id 
segment.
+  @Test
+  public void testVersionChangeBetweenBindAndAnalysis() {
+    PreparedNumericRowInfo info = new PreparedNumericRowInfo(DATABASE, 
request("tag", "v"));
+    InsertNumericRowStatement statement = bind(info, 1);
+    table.addColumnSchema(new TagColumnSchema("tag2", TSDataType.STRING));
+    validateFallback(statement);
+    assertArrayEquals(new String[] {"tag", "tag2", "v"}, 
statement.getMeasurements());
+    assertNull(statement.getFirstValueOfIndex(1));
+    assertEquals(2.5D, statement.getFirstValueOfIndex(2));
+    assertArrayEquals(new String[] {"tag", "v"}, bind(info, 
1).getMeasurements());
+  }
+
+  // A replacement table with the same name/revision must not reuse the old 
instance's schema.
+  @Test
+  public void testRecreatedTableUsesFullValidation() {
+    InsertNumericRowStatement statement =
+        bind(new PreparedNumericRowInfo(DATABASE, request("tag", "v")), 1);
+    TsTable replacement = new TsTable("t");
+    replacement.addColumnSchema(new TagColumnSchema("tag", TSDataType.STRING));
+    replacement.addColumnSchema(new FieldColumnSchema("v", TSDataType.DOUBLE));
+    assertEquals(table.getInstanceVersion().right, 
replacement.getInstanceVersion().right);
+    install(replacement);
+    validateFallback(statement);
+    assertNotNull(statement.getMeasurementSchemas()[1]);
+  }
+
+  // Missing columns/table, views and type conversions remain on normal 
creation/validation.
+  @Test
+  public void testIneligibleMetadataHasNoCachedSchema() {
+    assertNull(
+        bind(new PreparedNumericRowInfo(DATABASE, request("tag", "missing")), 
1)
+            .getPreparedTableVersion());
+    TSPrepareNumericRowReq req = request("tag", "v");
+    req.setDataTypes(Arrays.asList(TSDataType.STRING.ordinal(), 
TSDataType.INT64.ordinal()));
+    assertNull(
+        new PreparedNumericRowInfo(DATABASE, req)
+            .bind(1, tagBuffer(1), ByteBuffer.allocate(8))
+            .getPreparedTableVersion());
+    table.addProp(TreeViewSchema.TREE_PATH_PATTERN, "root.sg.**");
+    assertNull(
+        bind(new PreparedNumericRowInfo(DATABASE, request("tag", "v")), 1)
+            .getPreparedTableVersion());
+    DataNodeTableCache.getInstance().invalid(DATABASE);
+    assertNull(
+        bind(new PreparedNumericRowInfo(DATABASE, request("tag", "v")), 
1).getMeasurementSchemas());
+  }
+
+  // Reversed TAG input must take the normal path so payload values follow 
schema order.
+  @Test
+  public void testTagReorderingFallsBack() {
+    table.addColumnSchema(new TagColumnSchema("tag2", TSDataType.STRING));
+    InsertNumericRowStatement statement =
+        bind(new PreparedNumericRowInfo(DATABASE, request("tag2", "tag", 
"v")), 2);
+    assertNull(statement.getPreparedTableVersion());
+    validateFallback(statement);
+    assertArrayEquals(new String[] {"tag", "tag2", "v"}, 
statement.getMeasurements());
+    assertEquals("b", statement.getNumericValues().getTag(0));
+    assertEquals("a", statement.getNumericValues().getTag(1));
+  }
+
+  private void validateFallback(InsertNumericRowStatement statement) {
+    new InsertNumericRow(statement, context).validateTableSchema(metadata, 
context);
+    verify(metadata)
+        .validateInsertNodeMeasurements(eq(DATABASE), any(), eq(context), 
eq(true), any(), any());
+    assertNull(statement.getPreparedTableVersion());
+  }
+
+  private static void install(TsTable table) {
+    DataNodeTableCache.getInstance().preUpdateTable(DATABASE, table, null);
+    DataNodeTableCache.getInstance().commitUpdateTable(DATABASE, "t", null);
+  }
+
+  private static TSPrepareNumericRowReq request(String... names) {
+    TSPrepareNumericRowReq req = new TSPrepareNumericRowReq();
+    req.setTableName("t");
+    req.setMeasurements(Arrays.asList(names));
+    for (int i = 0; i < names.length; i++) {
+      boolean tag = i < names.length - 1;
+      req.addToDataTypes((tag ? TSDataType.STRING : 
TSDataType.DOUBLE).ordinal());
+      req.addToColumnCategories((byte) (tag ? ColumnCategory.TAG : 
ColumnCategory.FIELD).ordinal());
+    }
+    return req;
+  }
+
+  private static ByteBuffer tagBuffer(int count) {
+    ByteBuffer tags = ByteBuffer.allocate(count * 5);
+    for (int i = 0; i < count; i++) {
+      tags.putInt(1).put((byte) ('a' + i));
+    }
+    tags.flip();
+    return tags;
+  }
+
+  private static InsertNumericRowStatement bind(PreparedNumericRowInfo info, 
int tags) {
+    ByteBuffer values = ByteBuffer.allocate(8);
+    values.putDouble(2.5D).flip();
+    return info.bind(1, tagBuffer(tags), values);
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
index 14481bfa9bc..8ddabbd96d1 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java
@@ -375,6 +375,16 @@ public class TsTable {
     return new Pair<>(creationId, instanceVersion.get());
   }
 
+  /** Checks both table identity and schema revision, waiting for any ongoing 
schema write. */
+  public boolean matchesInstanceVersion(final Pair<Long, Long> version) {
+    readWriteLock.readLock().lock();
+    try {
+      return creationId.equals(version.left) && instanceVersion.get() == 
version.right;
+    } finally {
+      readWriteLock.readLock().unlock();
+    }
+  }
+
   public boolean containsPropWithoutLock(final String propKey) {
     return props != null && props.containsKey(propKey);
   }

Reply via email to