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

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

commit 4f31e9f2c83467042ec001d87151ffcb2877a568
Author: Tian Jiang <[email protected]>
AuthorDate: Wed Jul 1 12:08:55 2026 +0800

    add test
---
 .../relational/it/db/it/IoTDBDeletionTableIT.java  | 53 +++++++++++---
 .../iotdb/db/i18n/DataNodeQueryMessages.java       |  2 +
 .../iotdb/db/i18n/DataNodeQueryMessages.java       |  2 +
 .../db/queryengine/plan/analyze/AnalyzeUtils.java  | 50 ++++++++++++-
 .../dataregion/modification/TagPredicate.java      | 84 +++++++++++++++++++++-
 5 files changed, 179 insertions(+), 12 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
index e146af7e53e..022bb416dd6 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java
@@ -505,14 +505,7 @@ public class IoTDBDeletionTableIT {
       }
       assertEquals(List.of("d1,2,12", "d2,1,21", "d4,1,41", "d4,2,42"), 
actual);
 
-      try {
-        statement.execute("DELETE FROM delete_by_attr_other_ops WHERE attr1 IS 
NOT NULL");
-        fail("should not reach here!");
-      } catch (SQLException e) {
-        assertEquals(
-            "701: Unsupported expression: (attr1 IS NOT NULL) in (attr1 IS NOT 
NULL)",
-            e.getMessage());
-      }
+      statement.execute("DELETE FROM delete_by_attr_other_ops WHERE attr1 IS 
NOT NULL");
 
       actual = new ArrayList<>();
       try (ResultSet resultSet =
@@ -523,7 +516,49 @@ public class IoTDBDeletionTableIT {
               resultSet.getString(1) + "," + resultSet.getLong(2) + "," + 
resultSet.getInt(3));
         }
       }
-      assertEquals(List.of("d1,2,12", "d2,1,21", "d4,1,41", "d4,2,42"), 
actual);
+      assertEquals(List.of("d4,1,41", "d4,2,42"), actual);
+    }
+  }
+
+  @Test
+  public void testDeleteDataByTagIsNotNullFilter() throws SQLException {
+    try (Connection connection = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        Statement statement = connection.createStatement()) {
+      statement.execute("use test");
+      statement.execute(
+          "CREATE TABLE delete_by_tag_not_null("
+              + "deviceId STRING TAG, site STRING TAG, attr1 ATTRIBUTE, s1 
INT32 FIELD)");
+      statement.execute(
+          "INSERT INTO delete_by_tag_not_null(time, deviceId, site, attr1, s1) 
VALUES "
+              + "(1, 'd1', 'north', 'red', 11),"
+              + "(2, 'd1', 'north', 'red', 12),"
+              + "(1, 'd2', 'south', 'blue', 21),"
+              + "(2, 'd2', 'south', 'blue', 22)");
+      statement.execute(
+          "INSERT INTO delete_by_tag_not_null(time, deviceId, attr1, s1) 
VALUES "
+              + "(1, 'd3', 'green', 31),"
+              + "(2, 'd3', 'green', 32)");
+
+      statement.execute("DELETE FROM delete_by_tag_not_null WHERE site IS NOT 
NULL AND time = 1");
+
+      final List<String> actual = new ArrayList<>();
+      try (ResultSet resultSet =
+          statement.executeQuery(
+              "SELECT deviceId, site, time, s1 FROM delete_by_tag_not_null "
+                  + "ORDER BY deviceId, time")) {
+        while (resultSet.next()) {
+          actual.add(
+              resultSet.getString(1)
+                  + ","
+                  + resultSet.getString(2)
+                  + ","
+                  + resultSet.getLong(3)
+                  + ","
+                  + resultSet.getInt(4));
+        }
+      }
+      assertEquals(
+          List.of("d1,north,2,12", "d2,south,2,22", "d3,null,1,31", 
"d3,null,2,32"), actual);
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
index 9f8c4dd50b8..85dceb76a9a 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
@@ -271,6 +271,8 @@ public final class DataNodeQueryMessages {
       "The table '%s' does not contain a time column";
   public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR =
       "The operator of tag predicate must be '=' for ";
+  public static final String 
THE_COLUMN_S_DOES_NOT_EXIST_OR_IS_NOT_A_TAG_COLUMN =
+      "The column '%s' does not exist or is not a tag column";
   public static final String 
TOO_MANY_DEVICES_MATCHED_BY_ATTRIBUTE_FILTERS_IN_DELETION =
       "Too many devices (%d) matched by attribute filters in deletion, limit 
is %d. "
           + "Please remove all attribute filters (%s) or add more attribute 
filters.";
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
index 6d61d2bef70..a015779237b 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java
@@ -269,6 +269,8 @@ public final class DataNodeQueryMessages {
       "表 '%s' 不包含时间列";
   public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR =
       "标签谓词的运算符必须为 '=',目标:";
+  public static final String 
THE_COLUMN_S_DOES_NOT_EXIST_OR_IS_NOT_A_TAG_COLUMN =
+      "列 '%s' 不存在或不是标签列";
   public static final String 
TOO_MANY_DEVICES_MATCHED_BY_ATTRIBUTE_FILTERS_IN_DELETION =
       "删除语句中的属性过滤条件匹配了过多设备(%d),限制为 %d。请移除所有属性过滤条件(%s),或进一步增加属性过滤条件。";
   public static final String ONLY_TIME_FILTERS_ARE_SUPPORTED_IN_LAST_QUERY =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
index 598535b70a8..6557cb97142 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
@@ -29,6 +29,7 @@ import 
org.apache.iotdb.commons.partition.DataPartitionQueryParam;
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Expression;
 import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier;
+import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IsNotNullPredicate;
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IsNullPredicate;
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LogicalExpression;
 import 
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LogicalExpression.Operator;
@@ -66,6 +67,7 @@ import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.And;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.DeviceIn;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.SegmentExactMatch;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.SegmentNotNull;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.TSStatusCode;
 
@@ -522,6 +524,20 @@ public class AnalyzeUtils {
           }
           deviceFilterExpressions.add(toSymbolReferenceExpression(currExp));
         }
+      } else if (currExp instanceof IsNotNullPredicate) {
+        final PredicateParseResult parseResult =
+            parseIsNotNull((IsNotNullPredicate) currExp, tagPredicate, table);
+        tagPredicate = parseResult.tagPredicate;
+        if (parseResult.shouldQueryDevice()) {
+          if (Objects.isNull(deviceFilterExpressions)) {
+            deviceFilterExpressions = new ArrayList<>();
+          }
+          if (Objects.isNull(attributeColumns)) {
+            attributeColumns = new ArrayList<>();
+          }
+          deviceFilterExpressions.add(toSymbolReferenceExpression(currExp));
+          collectAttributeColumn(attributeColumns, 
parseResult.attributeColumn);
+        }
       } else {
         throw new SemanticException(
             DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + currExp + " in " + 
expression);
@@ -595,7 +611,9 @@ public class AnalyzeUtils {
     int tagColumnOrdinal = table.getTagColumnOrdinal(columnName);
     if (tagColumnOrdinal == -1) {
       throw new SemanticException(
-          "The column '" + columnName + "' does not exist or is not a tag 
column");
+          String.format(
+              
DataNodeQueryMessages.THE_COLUMN_S_DOES_NOT_EXIST_OR_IS_NOT_A_TAG_COLUMN,
+              columnName));
     }
 
     // the first segment is the table name, so + 1
@@ -603,6 +621,32 @@ public class AnalyzeUtils {
     return PredicateParseResult.tag(combinePredicates(oldPredicate, 
newPredicate));
   }
 
+  private static PredicateParseResult parseIsNotNull(
+      IsNotNullPredicate isNotNullPredicate, TagPredicate oldPredicate, 
TsTable table) {
+    Expression leftHandExp = isNotNullPredicate.getValue();
+    if (!(leftHandExp instanceof Identifier)) {
+      throw new SemanticException(
+          DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + 
leftHandExp);
+    }
+    String columnName = ((Identifier) leftHandExp).getValue();
+    final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
+    if (Objects.nonNull(columnSchema)
+        && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
+      return PredicateParseResult.attribute(columnName, oldPredicate);
+    }
+    int tagColumnOrdinal = table.getTagColumnOrdinal(columnName);
+    if (tagColumnOrdinal == -1) {
+      throw new SemanticException(
+          String.format(
+              
DataNodeQueryMessages.THE_COLUMN_S_DOES_NOT_EXIST_OR_IS_NOT_A_TAG_COLUMN,
+              columnName));
+    }
+
+    // the first segment is the table name, so + 1
+    TagPredicate newPredicate = new SegmentNotNull(tagColumnOrdinal + 1);
+    return PredicateParseResult.tag(combinePredicates(oldPredicate, 
newPredicate));
+  }
+
   private static TagPredicate combinePredicates(
       TagPredicate oldPredicate, TagPredicate newPredicate) {
     if (oldPredicate == null) {
@@ -673,7 +717,9 @@ public class AnalyzeUtils {
     int tagColumnOrdinal = table.getTagColumnOrdinal(columnName);
     if (tagColumnOrdinal == -1) {
       throw new SemanticException(
-          "The column '" + columnName + "' does not exist or is not a tag 
column");
+          String.format(
+              
DataNodeQueryMessages.THE_COLUMN_S_DOES_NOT_EXIST_OR_IS_NOT_A_TAG_COLUMN,
+              columnName));
     }
 
     TagPredicate newPredicate = getTagPredicate(comparisonExpression, right, 
tagColumnOrdinal);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
index 3a6f0a6ec35..8d41d23ca06 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
@@ -55,7 +55,8 @@ public abstract class TagPredicate implements 
StreamSerializable, BufferSerializ
     FULL_EXACT_MATCH,
     SEGMENT_EXACT_MATCH,
     AND,
-    DEVICE_IN;
+    DEVICE_IN,
+    SEGMENT_NOT_NULL;
 
     public long serialize(OutputStream stream) throws IOException {
       stream.write((byte) ordinal());
@@ -111,6 +112,8 @@ public abstract class TagPredicate implements 
StreamSerializable, BufferSerializ
       predicate = new And();
     } else if (Objects.requireNonNull(type) == TagPredicateType.DEVICE_IN) {
       predicate = new DeviceIn();
+    } else if (Objects.requireNonNull(type) == 
TagPredicateType.SEGMENT_NOT_NULL) {
+      predicate = new SegmentNotNull();
     } else {
       throw new 
IllegalArgumentException(StorageEngineMessages.UNRECOGNIZED_PREDICATE_TYPE + 
type);
     }
@@ -131,6 +134,8 @@ public abstract class TagPredicate implements 
StreamSerializable, BufferSerializ
       predicate = new And();
     } else if (Objects.requireNonNull(type) == TagPredicateType.DEVICE_IN) {
       predicate = new DeviceIn();
+    } else if (Objects.requireNonNull(type) == 
TagPredicateType.SEGMENT_NOT_NULL) {
+      predicate = new SegmentNotNull();
     } else {
       throw new 
IllegalArgumentException(StorageEngineMessages.UNRECOGNIZED_PREDICATE_TYPE + 
type);
     }
@@ -442,6 +447,83 @@ public abstract class TagPredicate implements 
StreamSerializable, BufferSerializ
     }
   }
 
+  public static class SegmentNotNull extends TagPredicate {
+
+    public static final long SHALLOW_SIZE =
+        RamUsageEstimator.shallowSizeOfInstance(SegmentNotNull.class);
+    private int segmentIndex;
+
+    public SegmentNotNull(int segmentIndex) {
+      super(TagPredicateType.SEGMENT_NOT_NULL);
+      this.segmentIndex = segmentIndex;
+    }
+
+    public SegmentNotNull() {
+      super(TagPredicateType.SEGMENT_NOT_NULL);
+    }
+
+    @Override
+    public int serializedSize() {
+      return super.serializedSize() + 
ReadWriteForEncodingUtils.varIntSize(segmentIndex);
+    }
+
+    @Override
+    public long serialize(OutputStream stream) throws IOException {
+      long size = super.serialize(stream);
+      size += ReadWriteForEncodingUtils.writeVarInt(segmentIndex, stream);
+      return size;
+    }
+
+    @Override
+    public long serialize(ByteBuffer buffer) {
+      long size = super.serialize(buffer);
+      size += ReadWriteForEncodingUtils.writeVarInt(segmentIndex, buffer);
+      return size;
+    }
+
+    @Override
+    public void deserialize(InputStream stream) throws IOException {
+      segmentIndex = ReadWriteForEncodingUtils.readVarInt(stream);
+    }
+
+    @Override
+    public void deserialize(ByteBuffer buffer) {
+      segmentIndex = ReadWriteForEncodingUtils.readVarInt(buffer);
+    }
+
+    @Override
+    public boolean matches(IDeviceID deviceID) {
+      return deviceID.segment(segmentIndex) != null;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SegmentNotNull that = (SegmentNotNull) o;
+      return segmentIndex == that.segmentIndex;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(segmentIndex);
+    }
+
+    @Override
+    public String toString() {
+      return "SegmentNotNull{" + "segmentIndex=" + segmentIndex + '}';
+    }
+
+    @Override
+    public long ramBytesUsed() {
+      return SHALLOW_SIZE;
+    }
+  }
+
   public static class And extends TagPredicate {
 
     public static final long SHALLOW_SIZE = 
RamUsageEstimator.shallowSizeOfInstance(And.class);

Reply via email to