diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/insertquery/IoTDBInsertQueryIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/insertquery/IoTDBInsertQueryIT.java
index b0cccba6f6cb9..44f14b4129bc0 100644
--- a/integration-test/src/test/java/org/apache/iotdb/relational/it/insertquery/IoTDBInsertQueryIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/insertquery/IoTDBInsertQueryIT.java
@@ -167,6 +167,83 @@ public void testSimple() throws SQLException {
}
}
+ /**
+ * End-to-end check for an {@code INSERT INTO ... SELECT} whose source tablet is not in ascending
+ * time order. The destination device is flushed at time 39, then the query feeds it another
+ * tablet: on the unfixed code the row at time 15 is classified as sequence data, a sequence
+ * TsFile overlapping [10,39] is created, and the query returns 15 after 39 (or twice).
+ *
+ *
The out-of-order input depends on the nested-loop cross join emitting the rows in probe
+ * order, i.e. {@code [100, 15]}. If the optimizer ever emits those rows in time order this test
+ * no longer reproduces the situation and would pass trivially; the guards that do not depend on
+ * the plan shape are {@code InsertTabletStatementGeneratorTest} (the tablet is sorted before it
+ * leaves the engine) and {@code
+ * DataRegionTest#testOutOfOrderTabletKeepsSequenceSpaceNonOverlapped} (the storage-layer
+ * classification of an unsorted tablet).
+ *
+ *
The first sequence file also has to hold several pages (see {@code
+ * max_number_of_points_in_page} in {@link #setUpClass()}): with a single page the overlapping
+ * page is merged before the earlier pages are emitted, which hides the wrong order as well.
+ */
+ @Test
+ public void testInsertIntoQueryWithOutOfOrderTabletDoesNotOverlapSequenceFiles()
+ throws SQLException {
+ try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+ Statement statement = connection.createStatement()) {
+ statement.execute("USE test");
+ statement.execute("CREATE TABLE overlap_dst(id STRING TAG, s INT32 FIELD)");
+ statement.execute("CREATE TABLE overlap_src(id STRING TAG, s INT32 FIELD)");
+
+ for (int time = 10; time <= 39; time++) {
+ statement.execute(
+ String.format("INSERT INTO overlap_dst(time,id,s) VALUES(%d,'x',%d)", time, time));
+ }
+ statement.execute("FLUSH");
+
+ statement.execute("INSERT INTO overlap_src(time,id,s) VALUES(100,'x',1),(15,'x',2)");
+ statement.execute(
+ "INSERT INTO overlap_dst (SELECT b.time, b.id, b.s FROM overlap_src a, overlap_src b "
+ + "WHERE a.id='x' AND b.id='x' AND a.time<>b.time)");
+ statement.execute("FLUSH");
+
+ List expectedTimes = new ArrayList<>();
+ for (long time = 10; time <= 39; time++) {
+ expectedTimes.add(time);
+ }
+ expectedTimes.add(100L);
+
+ try (ResultSet resultSet =
+ statement.executeQuery("SELECT time,s FROM overlap_dst WHERE id='x'")) {
+ List actualTimes = new ArrayList<>();
+ while (resultSet.next()) {
+ long time = resultSet.getLong(1);
+ actualTimes.add(time);
+ if (time == 15) {
+ Assert.assertEquals(2, resultSet.getInt(2));
+ }
+ }
+ Assert.assertEquals(expectedTimes, actualTimes);
+ }
+
+ // descending: the latest row 100 first, then 39 down to 10
+ List expectedDescTimes = new ArrayList<>();
+ expectedDescTimes.add(100L);
+ for (long time = 39; time >= 10; time--) {
+ expectedDescTimes.add(time);
+ }
+
+ try (ResultSet resultSet =
+ statement.executeQuery(
+ "SELECT time,s FROM overlap_dst WHERE id='x' ORDER BY time DESC")) {
+ List actualTimes = new ArrayList<>();
+ while (resultSet.next()) {
+ actualTimes.add(resultSet.getLong(1));
+ }
+ Assert.assertEquals(expectedDescTimes, actualTimes);
+ }
+ }
+ }
+
@Test
public void testInsertIntoSourceTable() throws SQLException {
try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java
index 5ca18623e12ed..e1f39e545808b 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java
@@ -26,8 +26,8 @@
import org.apache.iotdb.db.pipe.sink.util.builder.PipeTableModelTsFileBuilderV2;
import org.apache.iotdb.db.pipe.sink.util.builder.PipeTreeModelTsFileBuilderV2;
import org.apache.iotdb.db.pipe.sink.util.builder.PipeTsFileBuilder;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTableModelTabletEventSorter;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter;
+import org.apache.iotdb.db.utils.sorter.TableModelTabletEventSorter;
+import org.apache.iotdb.db.utils.sorter.TreeModelTabletEventSorter;
import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
import org.apache.tsfile.exception.write.WriteProcessException;
@@ -274,7 +274,7 @@ private void bufferTreeModelTablet(
final long creationTime,
final Tablet tablet,
final boolean isAligned) {
- new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
pipeName2WeightMap.compute(
new Pair<>(pipeName, creationTime),
@@ -285,7 +285,7 @@ private void bufferTreeModelTablet(
private void bufferTableModelTablet(
final String pipeName, final long creationTime, final Tablet tablet, final String dataBase) {
- new PipeTableModelTabletEventSorter(tablet).sortAndDeduplicateByDevIdTimestamp();
+ new TableModelTabletEventSorter(tablet).sortAndDeduplicateByDevIdTimestamp();
pipeName2WeightMap.compute(
new Pair<>(pipeName, creationTime),
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReq.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReq.java
index 00907ed8302f5..4d57cac059d6c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReq.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReq.java
@@ -26,8 +26,8 @@
import org.apache.iotdb.db.pipe.event.common.tablet.PipeTabletUtils;
import org.apache.iotdb.db.pipe.event.common.tablet.PipeTabletUtils.TabletStringInternPool;
import org.apache.iotdb.db.pipe.sink.util.TabletStatementConverter;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter;
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
+import org.apache.iotdb.db.utils.sorter.TreeModelTabletEventSorter;
import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
import org.apache.tsfile.enums.TSDataType;
@@ -81,12 +81,12 @@ public boolean getIsAligned() {
*/
public InsertTabletStatement constructStatement() {
if (statement != null) {
- new PipeTreeModelTabletEventSorter(statement).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(statement).deduplicateAndSortTimestampsIfNecessary();
return statement;
}
// Sort and deduplicate tablet before converting
- new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
try {
if (isTabletEmpty(tablet)) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
index dd6941d7f7d8f..ab14177a37083 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
@@ -27,9 +27,9 @@
import org.apache.iotdb.db.pipe.event.common.tablet.PipeTabletUtils;
import org.apache.iotdb.db.pipe.event.common.tablet.PipeTabletUtils.TabletStringInternPool;
import org.apache.iotdb.db.pipe.sink.util.TabletStatementConverter;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTableModelTabletEventSorter;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter;
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
+import org.apache.iotdb.db.utils.sorter.TableModelTabletEventSorter;
+import org.apache.iotdb.db.utils.sorter.TreeModelTabletEventSorter;
import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
import org.apache.tsfile.utils.PublicBAOS;
@@ -62,10 +62,10 @@ public InsertTabletStatement constructStatement() {
if (statement != null) {
if (isTableModel) {
- new PipeTableModelTabletEventSorter(statement).sortByTimestampIfNecessary();
+ new TableModelTabletEventSorter(statement).sortByTimestampIfNecessary();
statement.setWriteToTable(true);
} else {
- new PipeTreeModelTabletEventSorter(statement).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(statement).deduplicateAndSortTimestampsIfNecessary();
}
if (Objects.nonNull(dataBaseName)) {
statement.setDatabaseName(dataBaseName);
@@ -75,9 +75,9 @@ public InsertTabletStatement constructStatement() {
}
if (isTableModel) {
- new PipeTableModelTabletEventSorter(tablet).sortByTimestampIfNecessary();
+ new TableModelTabletEventSorter(tablet).sortByTimestampIfNecessary();
} else {
- new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
}
try {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcda/OpcDaServerHandle.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcda/OpcDaServerHandle.java
index 8bd84d1db72a0..566d72776fbd2 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcda/OpcDaServerHandle.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcda/OpcDaServerHandle.java
@@ -20,8 +20,8 @@
package org.apache.iotdb.db.pipe.sink.protocol.opcda;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
-import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter;
import org.apache.iotdb.db.utils.TypeServices;
+import org.apache.iotdb.db.utils.sorter.TreeModelTabletEventSorter;
import org.apache.iotdb.pipe.api.exception.PipeException;
import com.sun.jna.Memory;
@@ -186,7 +186,7 @@ static String getClsIDFromProgID(final String progID) {
}
void transfer(final Tablet tablet) {
- new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
+ new TreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary();
final List schemas = tablet.getSchemas();
for (int i = 0; i < schemas.size(); ++i) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
index 08a15eaddb5bd..a789219afd28b 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/process/TableInsertTabletStatementGenerator.java
@@ -24,6 +24,7 @@
import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
+import org.apache.iotdb.db.utils.sorter.TableModelTabletEventSorter;
import org.apache.tsfile.block.column.Column;
import org.apache.tsfile.enums.TSDataType;
@@ -106,6 +107,14 @@ public InsertTabletStatement constructInsertTabletStatement() {
InsertTabletStatement insertTabletStatement = super.constructInsertTabletStatement();
insertTabletStatement.setDatabaseName(databaseName);
insertTabletStatement.setWriteToTable(true);
+ // The rows of the SELECT output are not ordered: a join, an ORDER BY DESC or a scan over
+ // several devices can emit them in any order, while the storage layer documents that it
+ // assumes the rows of a device are ascending in time. Restore that order here, before the
+ // statement leaves the INSERT INTO path.
+ final TableModelTabletEventSorter sorter =
+ new TableModelTabletEventSorter(insertTabletStatement);
+ sorter.sortByTimestampIfNecessary();
+ insertTabletStatement.setOriginalRowIndexes(sorter.getRowPermutation());
return insertTabletStatement;
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
index 5c91d0af4eaaf..d747031856c5d 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
@@ -1362,6 +1362,12 @@ protected int checkTTLInternal(TSStatus[] results, long ttl, boolean breakOnFirs
/*
* assume that batch has been sorted by client
+ *
+ * A table-model tablet holds the rows of several devices, so the out-of-ttl rows are not
+ * necessarily a prefix: a row placed after firstAliveLoc can still be out of ttl, and the write
+ * path below does not reject it. SeriesScanUtil applies the ttl lower bound when it scans, so
+ * such a row stays invisible instead of being read back. Dropping it needs a change in the
+ * write path as well.
*/
int loc = 0;
int firstAliveLoc = -1;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java
index 639e35187a955..b0a112410531d 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/InsertTablet.java
@@ -98,7 +98,9 @@ public List