This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 321bba77b39 Fix NPE in ALIGN BY DEVICE queries when a device's
predicate is always true (#19009)
321bba77b39 is described below
commit 321bba77b39c30dd6cffdb154e761da85910c3ec
Author: Jackie Tien <[email protected]>
AuthorDate: Sat Oct 24 08:13:17 2026 +0800
Fix NPE in ALIGN BY DEVICE queries when a device's predicate is always true
(#19009)
---
.../db/it/alignbydevice/IoTDBAlignByDeviceIT.java | 17 +++++++++
.../iotdb/db/it/query/IoTDBNullOperandIT.java | 31 +++++++++++++++
.../queryengine/plan/analyze/AnalyzeVisitor.java | 9 +++--
.../db/queryengine/plan/analyze/AnalyzeTest.java | 44 ++++++++++++++++++++++
.../logical/DataQueryLogicalPlannerTest.java | 27 +++++++++++++
5 files changed, 125 insertions(+), 3 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceIT.java
index bf82d7a9950..2d6df6afe12 100644
---
a/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceIT.java
@@ -41,6 +41,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest;
import static org.junit.Assert.fail;
@RunWith(IoTDBTestRunner.class)
@@ -1127,6 +1128,22 @@ public class IoTDBAlignByDeviceIT {
}
}
+ @Test
+ public void selectWithNonExistMeasurementInAlwaysTrueWhereClause() {
+ // root.vehicle.d1 has no s1, so its predicate is always true, while
root.vehicle.d0 is still
+ // filtered by its own s1
+ resultSetEqualTest(
+ "select s0 from root.vehicle.* where s1 is null align by device",
+ new String[] {"Time", "Device", "s0"},
+ new String[] {
+ "106,root.vehicle.d0,99,", "1,root.vehicle.d1,999,",
"1000,root.vehicle.d1,888,",
+ });
+ resultSetEqualTest(
+ "select count(s0) from root.vehicle.* where s1 is null align by
device",
+ new String[] {"Device", "count(s0)"},
+ new String[] {"root.vehicle.d0,1,", "root.vehicle.d1,2,"});
+ }
+
/**
* data structure D, time, s1, s2; d1: 1, 10, 20; d2: 1, 12, 22; d3: 1,
null, 33
*
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBNullOperandIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBNullOperandIT.java
index f292b162155..0f161037814 100644
---
a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBNullOperandIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBNullOperandIT.java
@@ -279,4 +279,35 @@ public class IoTDBNullOperandIT {
expectedHeader,
retArray);
}
+
+ @Test
+ public void testSeriesNotExistInWhereAlignByDevice() {
+ // a predicate on a series that the device does not have is always true
for that device
+ String[] expectedHeader = new String[] {TIMESTAMP_STR, "Device", "s1",
"s3", "s4"};
+ String[] retArray =
+ new String[] {
+ "1,root.test.sg1,1,true,false,",
+ "2,root.test.sg1,2,true,null,",
+ "3,root.test.sg1,3,null,false,",
+ };
+ resultSetEqualTest(
+ "select s1, s3, s4 from root.** where notExist is null align by
device",
+ expectedHeader,
+ retArray);
+ resultSetEqualTest(
+ "select s1, s3, s4 from root.** where not(notExist > 0) align by
device",
+ expectedHeader,
+ retArray);
+ resultSetEqualTest(
+ "select s1, s3, s4 from root.** where notExist is null and time > 1
align by device",
+ expectedHeader,
+ new String[] {"2,root.test.sg1,2,true,null,",
"3,root.test.sg1,3,null,false,"});
+
+ expectedHeader = new String[] {"Device", "count(s1)"};
+ retArray = new String[] {"root.test.sg1,3,"};
+ resultSetEqualTest(
+ "select count(s1) from root.** where notExist is null align by device",
+ expectedHeader,
+ retArray);
+ }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
index 6781cc019c7..c9a422dec98 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
@@ -1485,9 +1485,12 @@ public class AnalyzeVisitor extends
StatementVisitor<Analysis, MPPQueryContext>
deviceToWhereExpression.entrySet()) {
IDeviceID deviceName = deviceWhereExpressionEntry.getKey();
Expression whereExpression = deviceWhereExpressionEntry.getValue();
- deviceToSourceExpressions
- .computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
- .addAll(searchSourceExpressions(whereExpression));
+ // null means the predicate of this device is always true, so it needs
no value filter
+ if (whereExpression != null) {
+ deviceToSourceExpressions
+ .computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
+ .addAll(searchSourceExpressions(whereExpression));
+ }
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeTest.java
index 05ce463d684..b0b53d31de3 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeTest.java
@@ -45,6 +45,7 @@ import
org.apache.iotdb.db.queryengine.plan.expression.leaf.ConstantOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimeSeriesOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimestampOperand;
import
org.apache.iotdb.db.queryengine.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.queryengine.plan.expression.unary.IsNullExpression;
import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator;
import org.apache.iotdb.db.queryengine.plan.statement.Statement;
import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement;
@@ -387,6 +388,49 @@ public class AnalyzeTest {
}
}
+ @Test
+ public void testAlignByDeviceWithAlwaysTruePredicate() throws
IllegalPathException {
+ // root.sg.d2 has no s3, so its predicate is simplified to TRUE and needs
no value filter
+ String[] sqls =
+ new String[] {
+ "select s1 from root.sg.d2 where s3 is null align by device",
+ "select s1 from root.sg.d2 where not(s3 > 1) align by device"
+ };
+ for (String sql : sqls) {
+ Analysis analysis = analyzeSQL(sql);
+
+ Assert.assertNull(sql,
analysis.getDeviceToWhereExpression().get(DEVICE2));
+ Assert.assertFalse(sql, analysis.hasValueFilter());
+ assertEquals(
+ sql,
+ ImmutableMap.of(
+ DEVICE2, Sets.newHashSet(new TimeSeriesOperand(new
PartialPath("root.sg.d2.s1")))),
+ analysis.getDeviceToSourceExpressions());
+ }
+ }
+
+ @Test
+ public void testAlignByDeviceWithAlwaysTruePredicateOnPartOfDevices()
+ throws IllegalPathException {
+ // root.sg.d1 is still filtered by its own s3, while the predicate of
root.sg.d2 is TRUE
+ Analysis analysis = analyzeSQL("select s1 from root.sg.* where s3 is null
align by device");
+
+ assertEquals(
+ new IsNullExpression(new TimeSeriesOperand(new
PartialPath("root.sg.d1.s3")), false),
+ analysis.getDeviceToWhereExpression().get(DEVICE1));
+ Assert.assertNull(analysis.getDeviceToWhereExpression().get(DEVICE2));
+ Assert.assertTrue(analysis.hasValueFilter());
+ assertEquals(
+ ImmutableMap.of(
+ DEVICE1,
+ Sets.newHashSet(
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+ new TimeSeriesOperand(new PartialPath("root.sg.d1.s3"))),
+ DEVICE2,
+ Sets.newHashSet(new TimeSeriesOperand(new
PartialPath("root.sg.d2.s1")))),
+ analysis.getDeviceToSourceExpressions());
+ }
+
@Test
public void testAggregationQueryAlignByDevice() {
String sql =
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/logical/DataQueryLogicalPlannerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/logical/DataQueryLogicalPlannerTest.java
index 1e88743f2ab..ab199047940 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/logical/DataQueryLogicalPlannerTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/logical/DataQueryLogicalPlannerTest.java
@@ -30,6 +30,7 @@ import
org.apache.iotdb.db.queryengine.plan.expression.binary.GreaterThanExpress
import
org.apache.iotdb.db.queryengine.plan.expression.binary.LogicAndExpression;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.ConstantOperand;
import org.apache.iotdb.db.queryengine.plan.expression.leaf.TimeSeriesOperand;
+import org.apache.iotdb.db.queryengine.plan.expression.unary.IsNullExpression;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.DeviceViewNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.FilterNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.GroupByLevelNode;
@@ -370,6 +371,32 @@ public class DataQueryLogicalPlannerTest {
Assert.assertEquals(actualPlan, limitNode);
}
+ @Test
+ public void testRawDataQueryAlignByDeviceWithAlwaysTruePredicate() {
+ // root.sg.d2 has no s3, so its predicate is always true and only
root.sg.d1 needs a FilterNode
+ String sql = "SELECT s1 FROM root.sg.* WHERE s3 IS NULL ALIGN BY DEVICE";
+
+ PlanNode actualPlan = parseSQLToPlanNode(sql);
+ Assert.assertTrue(actualPlan instanceof DeviceViewNode);
+ DeviceViewNode deviceViewNode = (DeviceViewNode) actualPlan;
+ Assert.assertEquals(
+ Arrays.asList(
+ IDeviceID.Factory.DEFAULT_FACTORY.create("root.sg.d1"),
+ IDeviceID.Factory.DEFAULT_FACTORY.create("root.sg.d2")),
+ deviceViewNode.getDevices());
+
+ PlanNode d1SubPlan = deviceViewNode.getChildren().get(0);
+ Assert.assertTrue(d1SubPlan instanceof FilterNode);
+ Assert.assertEquals(
+ new IsNullExpression(new
TimeSeriesOperand(schemaMap.get("root.sg.d1.s3")), false),
+ ((FilterNode) d1SubPlan).getPredicate());
+
+ PlanNode d2SubPlan = deviceViewNode.getChildren().get(1);
+ Assert.assertTrue(d2SubPlan instanceof SeriesScanNode);
+ Assert.assertEquals(
+ schemaMap.get("root.sg.d2.s1"), ((SeriesScanNode)
d2SubPlan).getSeriesPath());
+ }
+
@Test
public void testSimpleAggregationQuery() {
String sql =