This is an automated email from the ASF dual-hosted git repository.
Caideyipi pushed a commit to branch last-cache-fix
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/last-cache-fix by this push:
new c99aa26d3b0 Fix
c99aa26d3b0 is described below
commit c99aa26d3b0faee8a9b9904440d674453274cbdd
Author: Caideyipi <[email protected]>
AuthorDate: Fri May 15 00:18:15 2026 +0800
Fix
---
.../db/it/IoTDBMultiTAGsWithAttributesTableIT.java | 59 ++++++++++++++++++++++
.../planner/DataNodeTableOperatorGenerator.java | 4 ++
2 files changed, 63 insertions(+)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiTAGsWithAttributesTableIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiTAGsWithAttributesTableIT.java
index 5e18a8468c7..289ae7789a4 100644
---
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiTAGsWithAttributesTableIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiTAGsWithAttributesTableIT.java
@@ -20,6 +20,7 @@
package org.apache.iotdb.relational.it.db.it;
import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
import org.apache.iotdb.it.framework.IoTDBTestRunner;
import org.apache.iotdb.itbase.category.TableClusterIT;
import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
@@ -32,14 +33,17 @@ import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.sql.Connection;
+import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Arrays;
+import java.util.List;
import static
org.apache.iotdb.commons.queryengine.plan.relational.planner.node.JoinNode.JoinType.FULL;
import static
org.apache.iotdb.commons.queryengine.plan.relational.planner.node.JoinNode.JoinType.INNER;
import static
org.apache.iotdb.commons.queryengine.plan.relational.planner.node.JoinNode.JoinType.LEFT;
import static
org.apache.iotdb.commons.queryengine.plan.relational.planner.node.JoinNode.JoinType.RIGHT;
import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqual;
import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
import static org.junit.Assert.fail;
@@ -2466,6 +2470,8 @@ public class IoTDBMultiTAGsWithAttributesTableIT {
@Test
public void lastCacheTest() {
+ prepareStaleLastRowCacheOnSingleDataNode();
+
expectedHeader =
new String[] {
"level", "attr1", "device", "attr2", "_col4", "_col5", "_col6",
"_col7", "_col8", "_col9",
@@ -2925,6 +2931,59 @@ public class IoTDBMultiTAGsWithAttributesTableIT {
}
}
+ private static void prepareStaleLastRowCacheOnSingleDataNode() {
+ try {
+ final List<DataNodeWrapper> dataNodeWrappers =
EnvFactory.getEnv().getDataNodeWrapperList();
+ for (final DataNodeWrapper dataNodeWrapper : dataNodeWrappers) {
+ executeTableStatementOnSingleDataNode(dataNodeWrapper, "clear query
cache on local");
+ }
+
+ final DataNodeWrapper pollutedDataNode = dataNodeWrappers.get(0);
+
+ tableResultSetEqualOnSingleDataNode(
+ pollutedDataNode,
+ "select
last_by(num,time),last_by(bignum,time),last_by(floatnum,time) "
+ + "from table0 where device='d1' and level='l2' and
time<1971-04-26T17:46:40.000",
+ new String[] {"_col0", "_col1", "_col2"},
+ new String[] {"10,3147483648,231.55,"});
+ // This only refreshes the cached row time on one DataNode, keeping the
field caches stale.
+ tableResultSetEqualOnSingleDataNode(
+ pollutedDataNode,
+ "select last(time),last(device),last(level),last(attr1),last(attr2) "
+ + "from table0 where device='d1' and level='l2'",
+ new String[] {"_col0", "_col1", "_col2", "_col3", "_col4"},
+ new String[] {"1971-04-26T17:46:40.000Z,d1,l2,yy,zz,"});
+ } catch (final Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ private static void executeTableStatementOnSingleDataNode(
+ final DataNodeWrapper dataNodeWrapper, final String sql) throws
Exception {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(dataNodeWrapper, "root", "root",
"table");
+ final Statement statement = connection.createStatement()) {
+ statement.execute(sql);
+ }
+ }
+
+ private static void tableResultSetEqualOnSingleDataNode(
+ final DataNodeWrapper dataNodeWrapper,
+ final String sql,
+ final String[] expectedHeader,
+ final String[] expectedRetArray)
+ throws Exception {
+ try (final Connection connection =
+ EnvFactory.getEnv().getConnection(dataNodeWrapper, "root", "root",
"table");
+ final Statement statement = connection.createStatement()) {
+ statement.execute("use " + DATABASE_NAME);
+ try (final ResultSet resultSet = statement.executeQuery(sql)) {
+ tableResultSetEqual(resultSet, expectedHeader, expectedRetArray);
+ }
+ }
+ }
+
public static String[] buildHeaders(int length) {
String[] expectedHeader = new String[length];
for (int i = 0; i < length; i++) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/DataNodeTableOperatorGenerator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/DataNodeTableOperatorGenerator.java
index 06de6a11b82..5bdedd3d209 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/DataNodeTableOperatorGenerator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/DataNodeTableOperatorGenerator.java
@@ -1624,6 +1624,10 @@ public class DataNodeTableOperatorGenerator
for (int j = 0; j < lastByResult.get().getRight().length; j++) {
TsPrimitiveType tsPrimitiveType = lastByResult.get().getRight()[j];
if (tsPrimitiveType == null
+ // For last-row optimization, EMPTY means the local cache only
knows the latest row
+ // time, but cannot prove whether the field is truly null at
that row or simply
+ // stale under a newer cached row time. Fall back to scan to
guarantee correctness.
+ || tsPrimitiveType == EMPTY_PRIMITIVE_TYPE
|| (updateTimeFilter != null
&& !LastQueryUtil.satisfyFilter(
updateTimeFilter,