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

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


The following commit(s) were added to refs/heads/remove_swtich_type by this 
push:
     new 66d61e4f41e forbid new usages of switch
66d61e4f41e is described below

commit 66d61e4f41e43812a53984b02c45afcd5d2174bc
Author: Tian Jiang <[email protected]>
AuthorDate: Tue Sep 8 14:43:27 2026 +0800

    forbid new usages of switch
---
 iotdb-core/datanode/pom.xml                        |   5 +
 .../db/utils/TSDataTypeSwitchArchitectureTest.java |  39 +++++
 .../iotdb/db/utils/TSDataTypeSwitchRule.java       | 182 ++++++++++++++++++++
 .../iotdb/db/utils/TSDataTypeSwitchRuleTest.java   | 186 +++++++++++++++++++++
 4 files changed, 412 insertions(+)

diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml
index 91f2ffd3826..7e109b61ea9 100644
--- a/iotdb-core/datanode/pom.xml
+++ b/iotdb-core/datanode/pom.xml
@@ -318,6 +318,11 @@
             <version>1.3.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.ow2.asm</groupId>
+            <artifactId>asm</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchArchitectureTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchArchitectureTest.java
new file mode 100644
index 00000000000..7ac3339b973
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchArchitectureTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.utils;
+
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.ImportOption;
+import org.junit.Test;
+
+public class TSDataTypeSwitchArchitectureTest {
+
+  @Test
+  public void productionCodeMustUseTypeService() {
+    // Scan IoTDB production classes on this module's classpath, including its 
dependencies.
+    // Do not freeze violations or swallow failures: existing violations must 
fail this test too.
+    TSDataTypeSwitchRule.RULE.check(
+        new ClassFileImporter()
+            .withImportOption(new ImportOption.DoNotIncludeTests())
+            // Avoid building the entire server dependency graph: only switch 
owners need checking.
+            .withImportOption(location -> 
TSDataTypeSwitchRule.hasSwitch(location.asURI()))
+            .importPackages("org.apache.iotdb"));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRule.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRule.java
new file mode 100644
index 00000000000..6dad4d745e8
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRule.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.utils;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaModifier;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.type.service.TypeService;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+
+final class TSDataTypeSwitchRule {
+  private static final String SWITCH_MAP =
+      "$SwitchMap$" + TSDataType.class.getName().replace('.', '$');
+  private static final String SERVICE_DESCRIPTOR = 
Type.getDescriptor(TypeService.class);
+
+  static final ArchRule RULE =
+      classes()
+          .should(
+              new ArchCondition<>("only switch on TSDataType inside 
TypeService") {
+                @Override
+                public void check(JavaClass javaClass, ConditionEvents events) 
{
+                  if (javaClass.getModifiers().contains(JavaModifier.SYNTHETIC)
+                      || javaClass.isAssignableTo(TypeService.class)) {
+                    return;
+                  }
+                  SwitchVisitor visitor = 
read(javaClass.getSource().orElseThrow().getUri());
+                  for (SwitchAccess access : visitor.switches) {
+                    if (!visitor.serviceMethods.contains(access.method)) {
+                      events.add(
+                          SimpleConditionEvent.violated(
+                              javaClass,
+                              javaClass.getName()
+                                  + "."
+                                  + access.method
+                                  + " switches on TSDataType outside 
TypeService at ("
+                                  + visitor.sourceFile
+                                  + ":"
+                                  + access.line
+                                  + ")"));
+                    }
+                  }
+                }
+              })
+          .allowEmptyShould(true);
+
+  private TSDataTypeSwitchRule() {}
+
+  static boolean hasSwitch(URI uri) {
+    return !read(uri).switches.isEmpty();
+  }
+
+  private static SwitchVisitor read(URI uri) {
+    try (InputStream input = uri.toURL().openStream()) {
+      SwitchVisitor visitor = new SwitchVisitor();
+      new ClassReader(input).accept(visitor, ClassReader.SKIP_FRAMES);
+      return visitor;
+    } catch (IOException e) {
+      throw new UncheckedIOException("Cannot inspect " + uri, e);
+    }
+  }
+
+  // ArchUnit deliberately drops $SwitchMap$ accesses. Read them with ASM 
instead of treating
+  // every ordinal() invocation as a switch, which would incorrectly reject 
ordinary enum usage.
+  private static final class SwitchVisitor extends ClassVisitor {
+    private final List<SwitchAccess> switches = new ArrayList<>();
+    private final Set<String> serviceMethods = new HashSet<>();
+    private String owner;
+    private String sourceFile;
+    private boolean synthetic;
+
+    private SwitchVisitor() {
+      super(Opcodes.ASM9);
+    }
+
+    @Override
+    public void visit(
+        int version,
+        int access,
+        String name,
+        String signature,
+        String superName,
+        String[] interfaces) {
+      owner = name;
+      synthetic = (access & Opcodes.ACC_SYNTHETIC) != 0;
+    }
+
+    @Override
+    public void visitSource(String source, String debug) {
+      sourceFile = source;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] 
exceptions) {
+      if (synthetic) {
+        // The compiler's mapping-array initializer is not a source-level 
switch.
+        return null;
+      }
+      return new MethodVisitor(Opcodes.ASM9) {
+        private int line;
+
+        @Override
+        public void visitLineNumber(int lineNumber, Label start) {
+          line = lineNumber;
+        }
+
+        @Override
+        public void visitFieldInsn(
+            int opcode, String fieldOwner, String fieldName, String fieldType) 
{
+          // The repository compiles with Java 17 javac, for both switch 
statements and expressions.
+          if (opcode == Opcodes.GETSTATIC
+              && fieldName.equals(SWITCH_MAP)
+              && fieldType.equals("[I")) {
+            switches.add(new SwitchAccess(name + descriptor, line));
+          }
+        }
+
+        @Override
+        public void visitInvokeDynamicInsn(
+            String name, String descriptor, Handle bootstrap, Object... 
arguments) {
+          // Only the lambda implementing TypeService is exempt, not all 
methods of its holder.
+          if (bootstrap.getOwner().equals("java/lang/invoke/LambdaMetafactory")
+              && 
Type.getReturnType(descriptor).getDescriptor().equals(SERVICE_DESCRIPTOR)
+              && arguments.length > 1
+              && arguments[1] instanceof Handle implementation
+              && implementation.getOwner().equals(owner)
+              && implementation.getName().startsWith("lambda$")) {
+            serviceMethods.add(implementation.getName() + 
implementation.getDesc());
+          }
+        }
+      };
+    }
+  }
+
+  private static final class SwitchAccess {
+    private final String method;
+    private final int line;
+
+    private SwitchAccess(String method, int line) {
+      this.method = method;
+      this.line = line;
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRuleTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRuleTest.java
new file mode 100644
index 00000000000..53a0f2b4108
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TSDataTypeSwitchRuleTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.utils;
+
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.lang.EvaluationResult;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.type.Type;
+import org.apache.tsfile.read.common.type.service.TypeService;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.function.ToIntFunction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TSDataTypeSwitchRuleTest {
+
+  @Test
+  public void detectsStatementAndExpression() {
+    // Both javac switch forms must report the business method and a nonzero 
source line.
+    List<String> violations = violations(Switches.class);
+    assertEquals(2, violations.size());
+    assertTrue(violations.stream().anyMatch(message -> 
message.contains(".statement(")));
+    assertTrue(violations.stream().anyMatch(message -> 
message.contains(".expression(")));
+    for (String message : violations) {
+      assertTrue(message, 
message.matches(".*TSDataTypeSwitchRuleTest.java:[1-9][0-9]*.*"));
+    }
+  }
+
+  @Test
+  public void detectsLambdaAndAnonymousClass() {
+    // Switches cannot escape detection by being moved into lambdas or 
anonymous classes.
+    assertEquals(1, violations(LambdaSwitch.class).size());
+    assertEquals(1, violations(AnonymousSwitch.VALUE.getClass()).size());
+  }
+
+  @Test
+  public void allowsOtherEnumsAndOrdinal() {
+    // An ordinal read beside an unrelated enum switch is legal, as is reading 
an enum constant.
+    assertTrue(violations(OrdinaryUsage.class).isEmpty());
+  }
+
+  @Test
+  public void allowsTypeServiceImplementationAndRegisteredHolder() {
+    // Actual TypeService implementations and the existing holder are valid 
dispatch locations.
+    assertTrue(violations(SwitchService.class, TypeServices.class).isEmpty());
+  }
+
+  @Test
+  public void doesNotExemptSimilarlyNamedBusinessClass() {
+    // A class name containing TypeService is not sufficient to bypass the 
rule.
+    assertFalse(violations(NotATypeService.class).isEmpty());
+  }
+
+  private static List<String> violations(Class<?>... classes) {
+    EvaluationResult result =
+        TSDataTypeSwitchRule.RULE.evaluate(new 
ClassFileImporter().importClasses(classes));
+    return result.getFailureReport().getDetails();
+  }
+
+  @Test
+  public void onlyExemptsServiceLambdaInsideHolder() {
+    List<String> violations = violations(MixedHolder.class);
+    assertEquals(2, violations.size());
+    assertTrue(violations.stream().anyMatch(message -> 
message.contains(".business(")));
+  }
+
+  static class Switches {
+    int statement(TSDataType type) {
+      switch (type) {
+        case INT32:
+          return 1;
+        default:
+          return 0;
+      }
+    }
+
+    int expression(TSDataType type) {
+      return switch (type) {
+        case DOUBLE -> 2;
+        default -> 0;
+      };
+    }
+  }
+
+  static class LambdaSwitch {
+    ToIntFunction<TSDataType> value() {
+      return type ->
+          switch (type) {
+            case FLOAT -> 3;
+            default -> 0;
+          };
+    }
+  }
+
+  static class AnonymousSwitch {
+    static final ToIntFunction<TSDataType> VALUE =
+        new ToIntFunction<TSDataType>() {
+          @Override
+          public int applyAsInt(TSDataType type) {
+            return switch (type) {
+              case INT64 -> 4;
+              default -> 0;
+            };
+          }
+        };
+  }
+
+  static class OrdinaryUsage {
+    int value(TSDataType type, Thread.State state) {
+      int ordinal = type.ordinal();
+      int stateValue =
+          switch (state) {
+            case NEW -> 1;
+            default -> 0;
+          };
+      return ordinal + stateValue + TSDataType.INT32.ordinal();
+    }
+  }
+
+  static class SwitchService implements TypeService<Integer> {
+    @Override
+    public Integer call(Type type) {
+      return dispatch(TSDataType.INT32);
+    }
+
+    int dispatch(TSDataType type) {
+      return switch (type) {
+        case INT32 -> 1;
+        default -> 0;
+      };
+    }
+  }
+
+  static class NotATypeService {
+    int dispatch(TSDataType type) {
+      return switch (type) {
+        case INT32 -> 1;
+        default -> 0;
+      };
+    }
+  }
+
+  static class MixedHolder {
+    static final TypeService<Integer> SERVICE =
+        type ->
+            switch (TSDataType.INT32) {
+              case INT32 -> 1;
+              default -> 0;
+            };
+
+    static final ToIntFunction<TSDataType> ORDINARY =
+        type ->
+            switch (type) {
+              case INT32 -> 1;
+              default -> 0;
+            };
+
+    int business(TSDataType type) {
+      return switch (type) {
+        case INT32 -> 1;
+        default -> 0;
+      };
+    }
+  }
+}

Reply via email to