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;
+ };
+ }
+ }
+}