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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5405623efe8 Subscription: Fix DATE serialization for consensus dataset 
(#18227)
5405623efe8 is described below

commit 5405623efe838bfdc3616b779b39b8a0ad1a284f
Author: Caideyipi <[email protected]>
AuthorDate: Tue Jul 21 10:03:36 2026 +0800

    Subscription: Fix DATE serialization for consensus dataset (#18227)
    
    * Fix consensus subscription DATE tablet conversion
    
    * Update IoTDBConsensusSubscriptionDataTableIT.java
---
 .../IoTDBConsensusSubscriptionDataTableIT.java     | 114 +++++++++++++++++++++
 .../consensus/ConsensusLogToTabletConverter.java   |  44 +++++++-
 .../ConsensusLogToTabletConverterTest.java         |  33 ++++++
 3 files changed, 187 insertions(+), 4 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/subscription/it/consensus/local/tablemodel/IoTDBConsensusSubscriptionDataTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/subscription/it/consensus/local/tablemodel/IoTDBConsensusSubscriptionDataTableIT.java
index 1be9a1eb635..3d48bf31568 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/subscription/it/consensus/local/tablemodel/IoTDBConsensusSubscriptionDataTableIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/subscription/it/consensus/local/tablemodel/IoTDBConsensusSubscriptionDataTableIT.java
@@ -20,20 +20,29 @@
 package org.apache.iotdb.subscription.it.consensus.local.tablemodel;
 
 import org.apache.iotdb.isession.ITableSession;
+import org.apache.iotdb.isession.SessionDataSet;
 import org.apache.iotdb.it.env.EnvFactory;
 import org.apache.iotdb.it.framework.IoTDBTestRunner;
 import org.apache.iotdb.itbase.category.LocalStandaloneIT;
 import 
org.apache.iotdb.session.subscription.consumer.table.SubscriptionTablePullConsumer;
+import org.apache.iotdb.session.subscription.payload.SubscriptionMessage;
+import org.apache.iotdb.session.subscription.payload.SubscriptionRecordHandler;
 import 
org.apache.iotdb.subscription.it.consensus.local.AbstractSubscriptionConsensusLocalIT;
 
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.query.dataset.ResultSet;
+import org.apache.tsfile.write.record.Tablet;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
 
 import java.time.Duration;
+import java.time.LocalDate;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -51,6 +60,55 @@ public class IoTDBConsensusSubscriptionDataTableIT extends 
AbstractSubscriptionC
           + "s_bool BOOLEAN FIELD, "
           + "s_text TEXT FIELD";
 
+  @Test
+  public void testDateFieldInsertedByTablet() throws Exception {
+    final ConsensusSubscriptionTableITSupport.TestIdentifiers ids =
+        
ConsensusSubscriptionTableITSupport.newIdentifiers("table_data_date_tablet");
+    final String database = ids.getDatabase();
+    final String tableName = "table_0";
+    final LocalDate expectedDate = LocalDate.of(2026, 1, 31);
+    SubscriptionTablePullConsumer consumer = null;
+
+    try {
+      ConsensusSubscriptionTableITSupport.createDatabaseAndTable(
+          database, tableName, "device_id STRING TAG, value DATE FIELD");
+      ConsensusSubscriptionTableITSupport.createConsensusTopic(ids.getTopic(), 
database, tableName);
+      consumer =
+          ConsensusSubscriptionTableITSupport.createConsumer(
+              ids.getConsumerId(), ids.getConsumerGroupId());
+      consumer.subscribe(ids.getTopic());
+      ConsensusSubscriptionTableITSupport.pause(1_000L);
+
+      try (final ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+        session.executeNonQueryStatement("use " + database);
+        final Tablet tablet =
+            new Tablet(
+                tableName,
+                Arrays.asList("device_id", "value"),
+                Arrays.asList(TSDataType.STRING, TSDataType.DATE),
+                Arrays.asList(ColumnCategory.TAG, ColumnCategory.FIELD),
+                1);
+        tablet.addTimestamp(0, 1L);
+        tablet.addValue("device_id", 0, "device_0");
+        tablet.addValue("value", 0, expectedDate);
+        session.insert(tablet);
+        session.executeNonQueryStatement("flush");
+
+        try (final SessionDataSet dataSet =
+            session.executeQueryStatement("select count(*) from " + 
tableName)) {
+          Assert.assertTrue(dataSet.hasNext());
+          Assert.assertEquals(1L, 
dataSet.next().getFields().get(0).getLongV());
+          Assert.assertFalse(dataSet.hasNext());
+        }
+      }
+
+      assertDateFieldConsumed(consumer, database, tableName, expectedDate);
+      ConsensusSubscriptionTableITSupport.assertNoMoreMessages(consumer, 3, 
Duration.ofMillis(500));
+    } finally {
+      ConsensusSubscriptionTableITSupport.cleanup(consumer, ids.getTopic(), 
database);
+    }
+  }
+
   @Test
   public void testTypedRowsAcrossTimePartitions() throws Exception {
     final ConsensusSubscriptionTableITSupport.TestIdentifiers ids =
@@ -134,4 +192,60 @@ public class IoTDBConsensusSubscriptionDataTableIT extends 
AbstractSubscriptionC
       ConsensusSubscriptionTableITSupport.cleanup(consumer, ids.getTopic(), 
database);
     }
   }
+
+  private static void assertDateFieldConsumed(
+      final SubscriptionTablePullConsumer consumer,
+      final String database,
+      final String tableName,
+      final LocalDate expectedDate)
+      throws Exception {
+    for (int round = 0; round < 60; round++) {
+      final List<SubscriptionMessage> messages = 
consumer.poll(Duration.ofSeconds(1));
+      if (messages.isEmpty()) {
+        continue;
+      }
+
+      try {
+        int matchedRows = 0;
+        for (final SubscriptionMessage message : messages) {
+          for (final ResultSet resultSet : message.getResultSets()) {
+            final SubscriptionRecordHandler.SubscriptionResultSet 
subscriptionResultSet =
+                (SubscriptionRecordHandler.SubscriptionResultSet) resultSet;
+            if (!database.equals(subscriptionResultSet.getDatabaseName())
+                || !tableName.equals(subscriptionResultSet.getTableName())) {
+              continue;
+            }
+
+            final Tablet tablet = subscriptionResultSet.getTablet();
+            int dateColumnIndex = -1;
+            for (int columnIndex = 0; columnIndex < 
tablet.getSchemas().size(); columnIndex++) {
+              if 
("value".equals(tablet.getSchemas().get(columnIndex).getMeasurementName())) {
+                dateColumnIndex = columnIndex;
+                break;
+              }
+            }
+            Assert.assertTrue(
+                "DATE field is absent from the consumed tablet", 
dateColumnIndex >= 0);
+            Assert.assertEquals(
+                TSDataType.DATE, 
tablet.getSchemas().get(dateColumnIndex).getType());
+
+            for (int rowIndex = 0; rowIndex < tablet.getRowSize(); rowIndex++) 
{
+              if (tablet.getTimestamp(rowIndex) == 1L) {
+                Assert.assertEquals(expectedDate, tablet.getValue(rowIndex, 
dateColumnIndex));
+                matchedRows++;
+              }
+            }
+          }
+        }
+        if (matchedRows > 0) {
+          Assert.assertEquals(1, matchedRows);
+          return;
+        }
+      } finally {
+        consumer.commitSync(messages);
+      }
+    }
+
+    Assert.fail("DATE tablet row was not consumed within 60 poll rounds");
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverter.java
index fd4ac07678e..317f1f61ee9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverter.java
@@ -48,12 +48,14 @@ import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.utils.Binary;
 import org.apache.tsfile.utils.BitMap;
+import org.apache.tsfile.utils.DateUtils;
 import org.apache.tsfile.write.record.Tablet;
 import org.apache.tsfile.write.schema.IMeasurementSchema;
 import org.apache.tsfile.write.schema.MeasurementSchema;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -327,7 +329,11 @@ public class ConsensusLogToTabletConverter {
 
     for (int i = 0; i < columnCount; i++) {
       final int originalColIdx = allColumnsMatch ? i : 
matchedColumnIndices.get(i);
-      newColumns[i] = columns[originalColIdx];
+      newColumns[i] =
+          convertColumnToTabletRepresentation(
+              dataTypes[originalColIdx],
+              columns[originalColIdx],
+              bitMaps != null ? bitMaps[originalColIdx] : null);
       if (bitMaps != null && bitMaps[originalColIdx] != null) {
         newBitMaps[i] = bitMaps[originalColIdx];
       }
@@ -535,7 +541,11 @@ public class ConsensusLogToTabletConverter {
       }
       schemas.add(new MeasurementSchema(measurements[i], dataTypes[i]));
       columnTypes.add(toTsFileColumnCategory(node.getColumnCategories(), i));
-      validColumns.add(columns[i]);
+      validColumns.add(
+          convertColumnToTabletRepresentation(
+              dataTypes[i],
+              columns[i],
+              Objects.nonNull(bitMaps) && i < bitMaps.length ? bitMaps[i] : 
null));
       if (Objects.nonNull(validBitMaps)) {
         validBitMaps.add(i < bitMaps.length ? bitMaps[i] : null);
       }
@@ -709,9 +719,14 @@ public class ConsensusLogToTabletConverter {
         ((boolean[]) tablet.getValues()[columnIndex])[rowIndex] = (boolean) 
value;
         break;
       case INT32:
-      case DATE:
         ((int[]) tablet.getValues()[columnIndex])[rowIndex] = (int) value;
         break;
+      case DATE:
+        ((LocalDate[]) tablet.getValues()[columnIndex])[rowIndex] =
+            value instanceof LocalDate
+                ? (LocalDate) value
+                : DateUtils.parseIntToLocalDate((int) value);
+        break;
       case INT64:
       case TIMESTAMP:
         ((long[]) tablet.getValues()[columnIndex])[rowIndex] = (long) value;
@@ -753,10 +768,15 @@ public class ConsensusLogToTabletConverter {
             ((boolean[]) sourceColumn)[sourceRowIndex];
         break;
       case INT32:
-      case DATE:
         ((int[]) tablet.getValues()[targetColumnIndex])[targetRowIndex] =
             ((int[]) sourceColumn)[sourceRowIndex];
         break;
+      case DATE:
+        ((LocalDate[]) tablet.getValues()[targetColumnIndex])[targetRowIndex] =
+            sourceColumn instanceof LocalDate[]
+                ? ((LocalDate[]) sourceColumn)[sourceRowIndex]
+                : DateUtils.parseIntToLocalDate(((int[]) 
sourceColumn)[sourceRowIndex]);
+        break;
       case INT64:
       case TIMESTAMP:
         ((long[]) tablet.getValues()[targetColumnIndex])[targetRowIndex] =
@@ -788,6 +808,22 @@ public class ConsensusLogToTabletConverter {
     }
   }
 
+  private Object convertColumnToTabletRepresentation(
+      final TSDataType dataType, final Object column, final BitMap bitMap) {
+    if (dataType != TSDataType.DATE || !(column instanceof int[])) {
+      return column;
+    }
+
+    final int[] dateValues = (int[]) column;
+    final LocalDate[] localDateValues = new LocalDate[dateValues.length];
+    for (int i = 0; i < dateValues.length; i++) {
+      if (Objects.isNull(bitMap) || !bitMap.isMarked(i)) {
+        localDateValues[i] = DateUtils.parseIntToLocalDate(dateValues[i]);
+      }
+    }
+    return localDateValues;
+  }
+
   private static final class MatchedTreeRow {
 
     private final InsertRowNode rowNode;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverterTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverterTest.java
index c172dbe263d..1b8ad968365 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverterTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusLogToTabletConverterTest.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.pipe.datastructure.pattern.IoTDBTreePattern;
 import org.apache.iotdb.commons.pipe.datastructure.pattern.TablePattern;
 import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsOfOneDeviceNode;
@@ -37,12 +38,14 @@ import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.utils.Binary;
 import org.apache.tsfile.utils.BitMap;
+import org.apache.tsfile.utils.DateUtils;
 import org.apache.tsfile.write.record.Tablet;
 import org.apache.tsfile.write.schema.MeasurementSchema;
 import org.junit.Assert;
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -110,6 +113,36 @@ public class ConsensusLogToTabletConverterTest {
     Assert.assertSame(node.getColumns()[2], tablet.getValues()[1]);
   }
 
+  @Test
+  public void 
testConvertRelationalInsertTabletNodeConvertsDateForSerialization() throws 
Exception {
+    final ConsensusLogToTabletConverter converter = createConverter("value");
+    final LocalDate date = LocalDate.of(2026, 1, 31);
+    final RelationalInsertTabletNode node =
+        new RelationalInsertTabletNode(
+            new PlanNodeId("date"),
+            new PartialPath(new String[] {StatementTestUtils.tableName()}),
+            true,
+            new String[] {"device_id", "value"},
+            new TSDataType[] {TSDataType.STRING, TSDataType.DATE},
+            new long[] {1L},
+            null,
+            new Object[] {
+              new Binary[] {new 
Binary("device_0".getBytes(StandardCharsets.UTF_8))},
+              new int[] {DateUtils.parseDateExpressionToInt(date)}
+            },
+            1,
+            new TsTableColumnCategory[] {TsTableColumnCategory.TAG, 
TsTableColumnCategory.FIELD});
+
+    final List<Tablet> tablets = converter.convert(node);
+
+    Assert.assertEquals(1, tablets.size());
+    final Tablet tablet = tablets.get(0);
+    Assert.assertArrayEquals(new LocalDate[] {date}, (LocalDate[]) 
tablet.getValues()[1]);
+    final Tablet deserializedTablet = Tablet.deserialize(tablet.serialize());
+    Assert.assertArrayEquals(
+        new LocalDate[] {date}, (LocalDate[]) 
deserializedTablet.getValues()[1]);
+  }
+
   @Test
   public void 
testConvertRelationalInsertTabletNodeKeepsNullMatchedFieldColumn() {
     final ConsensusLogToTabletConverter converter = createConverter("m1");

Reply via email to