From de140b41968d6e20cc454b75d3e44bbdb136c445 Mon Sep 17 00:00:00 2001 From: ColinLee Date: Wed, 14 Oct 2026 18:23:42 +0800 Subject: [PATCH 1/2] Fix nullable AND results in tree-model CASE --- .../db/it/query/IoTDBCaseWhenThenIT.java | 16 ++++++ .../binary/LogicAndColumnTransformer.java | 2 +- .../LogicBinaryColumnTransformerTest.java | 56 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java index a88b89e90b5da..b467f0139e156 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java @@ -928,4 +928,20 @@ public void testThenWithBinarySameConstant() { new String[] {"0,0.0,", "1000000,11.0,", "20000000,22.0,", "210000000,33.0,"}; resultSetEqualTest(sql, expectedHeader, retArray); } + + @Test + public void testNullableAndInLaterCaseBranch() { + prepareData( + new String[] { + "CREATE TIMESERIES root.sg.case_null.s1 WITH DATATYPE=INT32, ENCODING=PLAIN", + "CREATE TIMESERIES root.sg.case_null.s2 WITH DATATYPE=INT32, ENCODING=PLAIN", + "INSERT INTO root.sg.case_null(time, s2) VALUES (1, 1)", + "INSERT INTO root.sg.case_null(time, s1, s2) VALUES (2, 1, 1), (3, 0, 1), (4, 101, 1)" + }); + resultSetEqualTest( + "SELECT CASE WHEN s1 > 100 THEN 1 WHEN s1 > 0 AND s2 > 0 THEN 2 ELSE 3 END AS result " + + "FROM root.sg.case_null", + new String[] {TIMESTAMP_STR, "result"}, + new String[] {"1,3.0,", "2,2.0,", "3,3.0,", "4,1.0,"}); + } } diff --git a/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/transformation/dag/column/binary/LogicAndColumnTransformer.java b/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/transformation/dag/column/binary/LogicAndColumnTransformer.java index 5e7197eaab893..69cf49b96445b 100644 --- a/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/transformation/dag/column/binary/LogicAndColumnTransformer.java +++ b/iotdb-core/calc-commons/src/main/java/org/apache/iotdb/calc/transformation/dag/column/binary/LogicAndColumnTransformer.java @@ -77,7 +77,7 @@ public void evaluateWithSelection(boolean[] selection) { } } - initializeColumnCache(rightColumn); + initializeColumnCache(builder.build()); this.leftTransformer.clearCache(); this.rightTransformer.clearCache(); } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java index 086a143c86d33..9e1a72d86ae78 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java @@ -113,4 +113,60 @@ public void testInputType() { } } + + @Test + public void testLogicAndWithNullsAndSelection() { + assertSelectedAnd(false); + } + + @Test + public void testLogicAndWithNullsAndPartialSelection() { + assertSelectedAnd(true); + } + + private void assertSelectedAnd(boolean partial) { + Boolean[] values = {null, false, true}; + Boolean[] expected = {null, false, null, false, false, false, null, false, true}; + TsBlockBuilder builder = + new TsBlockBuilder(9, Arrays.asList(TSDataType.BOOLEAN, TSDataType.BOOLEAN)); + for (Boolean left : values) { + for (Boolean right : values) { + builder.getTimeColumnBuilder().writeLong(0); + if (left == null) { + builder.getColumnBuilder(0).appendNull(); + } else { + builder.getColumnBuilder(0).writeBoolean(left); + } + if (right == null) { + builder.getColumnBuilder(1).appendNull(); + } else { + builder.getColumnBuilder(1).writeBoolean(right); + } + builder.declarePosition(); + } + } + TsBlock block = builder.build(); + IdentityColumnTransformer left = new IdentityColumnTransformer(returnType, 0); + IdentityColumnTransformer right = new IdentityColumnTransformer(returnType, 1); + left.addReferenceCount(); + right.addReferenceCount(); + left.initFromTsBlock(block); + right.initFromTsBlock(block); + LogicAndColumnTransformer transformer = new LogicAndColumnTransformer(returnType, left, right); + transformer.addReferenceCount(); + boolean[] selection = new boolean[9]; + for (int i = 0; i < selection.length; i++) { + selection[i] = !partial || i % 2 == 0; + } + transformer.evaluateWithSelection(selection); + Column result = transformer.getColumn(); + Assert.assertEquals(expected.length, result.getPositionCount()); + for (int i = 0; i < expected.length; i++) { + if (!selection[i] || expected[i] == null) { + Assert.assertTrue(result.isNull(i)); + } else { + Assert.assertEquals(expected[i], result.getBoolean(i)); + } + } + } } From 3316fa671657beb39ac63af39e3dcdb1827ab02d Mon Sep 17 00:00:00 2001 From: ColinLee Date: Fri, 16 Oct 2026 14:43:40 +0800 Subject: [PATCH 2/2] Strengthen nullable AND and OR selection regression tests --- .../db/it/query/IoTDBCaseWhenThenIT.java | 10 ++++ .../LogicBinaryColumnTransformerTest.java | 48 ++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java index b467f0139e156..8a7933ffecffd 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java @@ -943,5 +943,15 @@ public void testNullableAndInLaterCaseBranch() { + "FROM root.sg.case_null", new String[] {TIMESTAMP_STR, "result"}, new String[] {"1,3.0,", "2,2.0,", "3,3.0,", "4,1.0,"}); + resultSetEqualTest( + "SELECT CASE WHEN s1 > 100 THEN 1 WHEN NOT (s1 > 0 AND s2 > 0) THEN 2 ELSE 3 END AS result " + + "FROM root.sg.case_null", + new String[] {TIMESTAMP_STR, "result"}, + new String[] {"1,3.0,", "2,3.0,", "3,2.0,", "4,1.0,"}); + resultSetEqualTest( + "SELECT CASE WHEN s1 > 100 THEN false WHEN s2 > 0 THEN (s1 > 0 AND s2 > 0) END AS result " + + "FROM root.sg.case_null", + new String[] {TIMESTAMP_STR, "result"}, + new String[] {"1,null,", "2,true,", "3,false,", "4,false,"}); } } diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java index 9e1a72d86ae78..018dcefce5fdf 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/column/binary/LogicBinaryColumnTransformerTest.java @@ -19,6 +19,7 @@ package org.apache.iotdb.db.queryengine.transformation.dag.column.binary; +import org.apache.iotdb.calc.transformation.dag.column.ColumnTransformer; import org.apache.iotdb.calc.transformation.dag.column.binary.BinaryColumnTransformer; import org.apache.iotdb.calc.transformation.dag.column.binary.LogicAndColumnTransformer; import org.apache.iotdb.calc.transformation.dag.column.binary.LogicOrColumnTransformer; @@ -37,6 +38,7 @@ import org.junit.Test; import java.util.Arrays; +import java.util.function.BiFunction; public class LogicBinaryColumnTransformerTest { @@ -44,6 +46,14 @@ public class LogicBinaryColumnTransformerTest { private static final int POSITION_COUNT = 4; + private static final Boolean[] AND_EXPECTED = { + null, false, null, false, false, false, null, false, true + }; + + private static final Boolean[] OR_EXPECTED = { + null, null, true, null, false, true, true, true, true + }; + private static final boolean[] leftInput = new boolean[] {true, false, true, false}; private static final boolean[] rightInput = new boolean[] {true, false, true, false}; @@ -116,17 +126,37 @@ public void testInputType() { @Test public void testLogicAndWithNullsAndSelection() { - assertSelectedAnd(false); + assertSelected( + (left, right) -> new LogicAndColumnTransformer(returnType, left, right), + AND_EXPECTED, + false); } @Test public void testLogicAndWithNullsAndPartialSelection() { - assertSelectedAnd(true); + assertSelected( + (left, right) -> new LogicAndColumnTransformer(returnType, left, right), + AND_EXPECTED, + true); + } + + @Test + public void testLogicOrWithNullsAndSelection() { + assertSelected( + (left, right) -> new LogicOrColumnTransformer(returnType, left, right), OR_EXPECTED, false); + } + + @Test + public void testLogicOrWithNullsAndPartialSelection() { + assertSelected( + (left, right) -> new LogicOrColumnTransformer(returnType, left, right), OR_EXPECTED, true); } - private void assertSelectedAnd(boolean partial) { + private void assertSelected( + BiFunction transformerFactory, + Boolean[] expected, + boolean partial) { Boolean[] values = {null, false, true}; - Boolean[] expected = {null, false, null, false, false, false, null, false, true}; TsBlockBuilder builder = new TsBlockBuilder(9, Arrays.asList(TSDataType.BOOLEAN, TSDataType.BOOLEAN)); for (Boolean left : values) { @@ -152,19 +182,23 @@ private void assertSelectedAnd(boolean partial) { right.addReferenceCount(); left.initFromTsBlock(block); right.initFromTsBlock(block); - LogicAndColumnTransformer transformer = new LogicAndColumnTransformer(returnType, left, right); + BinaryColumnTransformer transformer = transformerFactory.apply(left, right); transformer.addReferenceCount(); boolean[] selection = new boolean[9]; for (int i = 0; i < selection.length; i++) { - selection[i] = !partial || i % 2 == 0; + // Keep the right-FALSE rows (1 and 7) selected while excluding some left-FALSE rows. + selection[i] = !partial || (i != 3 && i != 5); } + boolean[] selected = selection.clone(); transformer.evaluateWithSelection(selection); + Assert.assertArrayEquals(selected, selection); Column result = transformer.getColumn(); Assert.assertEquals(expected.length, result.getPositionCount()); for (int i = 0; i < expected.length; i++) { - if (!selection[i] || expected[i] == null) { + if (!selected[i] || expected[i] == null) { Assert.assertTrue(result.isNull(i)); } else { + Assert.assertFalse(result.isNull(i)); Assert.assertEquals(expected[i], result.getBoolean(i)); } }