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

Caideyipi pushed a commit to branch fix/delete-attribute-tag-npe
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit dfe0bfe736f877a6d9179b431d1fb3a659849b33
Author: Caideyipi <[email protected]>
AuthorDate: Mon Nov 2 19:15:47 2026 +0800

    Fix table DELETE filters with mixed-case columns
---
 .../relational/it/db/it/IoTDBDeletionTableIT.java  | 92 ++++++++++++++++++++++
 .../db/queryengine/plan/analyze/AnalyzeUtils.java  | 14 ++--
 2 files changed, 100 insertions(+), 6 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 0f29879caad..6b979e287c9 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
@@ -33,6 +33,7 @@ import org.apache.iotdb.itbase.env.BaseEnv;
 import org.apache.iotdb.itbase.exception.ParallelRequestTimeoutException;
 import org.apache.iotdb.rpc.IoTDBConnectionException;
 import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.tsfile.enums.ColumnCategory;
 import org.apache.tsfile.enums.TSDataType;
@@ -935,16 +936,107 @@ public class IoTDBDeletionTableIT {
         builder.append("(1, 'd").append(i).append("', 'same', 
").append(i).append(')');
       }
       statement.execute(builder.toString());
+      statement.execute("FLUSH");
+      statement.execute(
+          "INSERT INTO delete_by_attr_limit(time,deviceId,attr1,s1) VALUES"
+              + "(2,'d0','same',2000),(2,'d1000','same',3000)");
+      statement.execute(
+          "CREATE TABLE delete_by_attr_guard(deviceId STRING TAG, attr1 
ATTRIBUTE, s1 INT32 FIELD)");
+      statement.execute(
+          "INSERT INTO delete_by_attr_guard(time,deviceId,attr1,s1) 
VALUES(1,'d0','same',9000)");
+      final List<String> expected = new ArrayList<>();
+      for (int i = 0; i <= 1000; i++) {
+        expected.add("1,d" + i + ",same," + i + ",");
+      }
+      expected.add("2,d0,same,2000,");
+      expected.add("2,d1000,same,3000,");
 
       try {
         statement.execute("DELETE FROM delete_by_attr_limit WHERE attr1 = 
'same'");
         fail("should not reach here!");
       } catch (SQLException e) {
+        assertEquals(TSStatusCode.SEMANTIC_ERROR.getStatusCode(), 
e.getErrorCode());
         assertTrue(e.getMessage().contains("Too many devices (1001)"));
         assertTrue(e.getMessage().contains("limit is 1000"));
         assertTrue(e.getMessage().contains("attr1"));
         assertTrue(e.getMessage().contains("Please remove all attribute 
filters"));
       }
+
+      // The analyzer rejects the complete operation before dispatching any 
data deletion.
+      // Check every key/value, including disk and memory data, rather than 
only the exception.
+      assertAttributeLimitRows(statement, expected);
+      // A narrower ATTRIBUTE + TAG predicate must work after the rejected 
request.
+      statement.execute(
+          "DELETE FROM delete_by_attr_limit WHERE attr1='same' AND 
deviceId='d0' AND time=1");
+      expected.remove("1,d0,same,0,");
+      statement.execute("FLUSH");
+      assertAttributeLimitRows(statement, expected);
+      try (ResultSet rows =
+          statement.executeQuery(
+              "SELECT cast(time AS INT64) AS t,deviceId,attr1,s1 FROM 
delete_by_attr_guard")) {
+        TestUtils.assertResultSetEqual(
+            rows, "t,deviceId,attr1,s1,", new String[] {"1,d0,same,9000,"});
+      }
+    }
+  }
+
+  @Test
+  public void testAttributeDeleteWithMixedCaseColumnNames() 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_mixed_case(DeviceId STRING TAG, Attr1 
ATTRIBUTE, s1 INT32 FIELD)");
+      statement.execute(
+          "INSERT INTO delete_mixed_case(time,DeviceId,Attr1,s1) VALUES"
+              + "(1,'d0','same',10),(2,'d0','same',20),(1,'D0','same',30),"
+              + "(1,'d1','Other',40),(2,'d1','Other',41),(1,'d2',null,50),"
+              + "(1,'d3','Other',60),(1,'keep','same',70)");
+      final List<String> expected =
+          new ArrayList<>(
+              List.of(
+                  "1,d0,same,10,",
+                  "2,d0,same,20,",
+                  "1,D0,same,30,",
+                  "1,d1,Other,40,",
+                  "2,d1,Other,41,",
+                  "1,d2,null,50,",
+                  "1,d3,Other,60,",
+                  "1,keep,same,70,"));
+      final String[][] deletions = {
+        // This first request reproduced the missing-symbol NPE without a 
preceding rejection.
+        {"attr1='same' AND deviceId='d0' AND time=1", "1,d0,same,10,"},
+        {"ATTR1='same' AND DEVICEID='D0' AND TIME=1", "1,D0,same,30,"},
+        {"AtTr1 IS NULL AND DeViCeId='d2' AND TiMe=1", "1,d2,null,50,"},
+        {"AtTr1 LIKE 'Ot%' AND DeViCeId='d1' AND TiMe=1", "1,d1,Other,40,"},
+        {"AtTr1 IN ('Other') AND DeViCeId='d3' AND TiMe=1", "1,d3,Other,60,"},
+        {"AtTr1 IS NOT NULL AND DeViCeId='d0' AND TiMe=2", "2,d0,same,20,"}
+      };
+      for (String[] deletion : deletions) {
+        statement.execute("DELETE FROM delete_mixed_case WHERE " + 
deletion[0]);
+        assertTrue(expected.remove(deletion[1]));
+        assertMixedCaseAttributeRows(statement, expected);
+      }
+      statement.execute("FLUSH");
+      assertMixedCaseAttributeRows(statement, expected);
+    }
+  }
+
+  private static void assertMixedCaseAttributeRows(Statement statement, 
List<String> expected)
+      throws SQLException {
+    try (ResultSet rows =
+        statement.executeQuery(
+            "SELECT cast(time AS INT64) AS t,deviceId,attr1,s1 FROM 
delete_mixed_case")) {
+      TestUtils.assertResultSetEqualUnordered(rows, "t,deviceId,attr1,s1,", 
expected);
+    }
+  }
+
+  private static void assertAttributeLimitRows(Statement statement, 
List<String> expected)
+      throws SQLException {
+    try (ResultSet rows =
+        statement.executeQuery(
+            "SELECT cast(time AS INT64) AS t,deviceId,attr1,s1 FROM 
delete_by_attr_limit")) {
+      TestUtils.assertResultSetEqualUnordered(rows, "t,deviceId,attr1,s1,", 
expected);
     }
   }
 
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 c65d39a0769..4e2a9de0cd4 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
@@ -88,6 +88,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Queue;
@@ -643,7 +644,7 @@ public class AnalyzeUtils {
       throw new SemanticException(
           DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + 
leftHandExp);
     }
-    String columnName = ((Identifier) leftHandExp).getValue();
+    String columnName = ((Identifier) 
leftHandExp).getValue().toLowerCase(Locale.ENGLISH);
     final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
     if (Objects.nonNull(columnSchema)
         && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
@@ -669,7 +670,7 @@ public class AnalyzeUtils {
       throw new SemanticException(
           DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + 
leftHandExp);
     }
-    String columnName = ((Identifier) leftHandExp).getValue();
+    String columnName = ((Identifier) 
leftHandExp).getValue().toLowerCase(Locale.ENGLISH);
     final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
     if (Objects.nonNull(columnSchema)
         && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
@@ -695,7 +696,7 @@ public class AnalyzeUtils {
       throw new SemanticException(
           DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + 
leftHandExp);
     }
-    String columnName = ((Identifier) leftHandExp).getValue();
+    String columnName = ((Identifier) 
leftHandExp).getValue().toLowerCase(Locale.ENGLISH);
     final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
     if (Objects.nonNull(columnSchema)
         && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
@@ -721,7 +722,7 @@ public class AnalyzeUtils {
       throw new SemanticException(
           DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + 
leftHandExp);
     }
-    String columnName = ((Identifier) leftHandExp).getValue();
+    String columnName = ((Identifier) 
leftHandExp).getValue().toLowerCase(Locale.ENGLISH);
     final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
     if (Objects.nonNull(columnSchema)
         && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
@@ -815,7 +816,7 @@ public class AnalyzeUtils {
       return PredicateParseResult.time(oldPredicate);
     }
     // tag predicate
-    String columnName = identifier.getValue();
+    String columnName = identifier.getValue().toLowerCase(Locale.ENGLISH);
     final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName);
     if (Objects.nonNull(columnSchema)
         && 
columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) {
@@ -926,7 +927,8 @@ public class AnalyzeUtils {
               final Identifier node,
               final Void context,
               final ExpressionTreeRewriter<Void> treeRewriter) {
-            return new SymbolReference(node.getValue());
+            // DELETE bypasses normal column resolution; use the schema's 
canonical names.
+            return new 
SymbolReference(node.getValue().toLowerCase(Locale.ENGLISH));
           }
         },
         expression);

Reply via email to