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

zabetak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a1df0cd44 [CALCITE-7712] Enforce Input annotation on 
ReflectiveSchema.Factory operands
9a1df0cd44 is described below

commit 9a1df0cd4434c8219d06f6f274190b480c33e6d6
Author: Stamatis Zampetakis <[email protected]>
AuthorDate: Thu Aug 13 16:02:13 2026 +0300

    [CALCITE-7712] Enforce Input annotation on ReflectiveSchema.Factory operands
---
 .../calcite/adapter/java/ReflectiveSchema.java     | 23 ++++++++-
 .../apache/calcite/test/ReflectiveSchemaTest.java  | 58 ++++++++++++++++++++++
 site/_docs/history.md                              |  5 ++
 3 files changed, 84 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/java/ReflectiveSchema.java 
b/core/src/main/java/org/apache/calcite/adapter/java/ReflectiveSchema.java
index 24dbe8a3d2..07e29b2b22 100644
--- a/core/src/main/java/org/apache/calcite/adapter/java/ReflectiveSchema.java
+++ b/core/src/main/java/org/apache/calcite/adapter/java/ReflectiveSchema.java
@@ -56,6 +56,10 @@
 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -293,6 +297,15 @@ private static class ReflectiveTable
     }
   }
 
+  /**
+   * Designates a type that can be used as input in the {@link Factory}.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface Input {
+
+  }
+
   /** Factory that creates a schema by instantiating an object and looking at
    * its public fields.
    *
@@ -321,22 +334,28 @@ private static class ReflectiveTable
    *   Employee[] EMPS;
    *   Department[] DEPTS;
    * }</pre></blockquote>
+   *
+   * <p>The class operand must be annotated as {@link Input} otherwise it 
cannot
+   * be used in this factory.
    */
   public static class Factory implements SchemaFactory {
     @Override public Schema create(SchemaPlus parentSchema, String name,
         Map<String, Object> operand) {
       Class<?> clazz;
       Object target;
-      final Object className = operand.get("class");
+      final String className = (String) operand.get("class");
       if (className != null) {
         try {
-          clazz = Class.forName((String) className);
+          clazz = Class.forName(className, false, 
Factory.class.getClassLoader());
         } catch (ClassNotFoundException e) {
           throw new RuntimeException("Error loading class " + className, e);
         }
       } else {
         throw new RuntimeException("Operand 'class' is required");
       }
+      if (!clazz.isAnnotationPresent(Input.class)) {
+        throw new IllegalArgumentException(clazz + " is not annotated with 
@Input");
+      }
       final Object methodName = operand.get("staticMethod");
       if (methodName != null) {
         try {
diff --git 
a/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java 
b/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
index 3c86c01255..e4979b95f3 100644
--- a/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
+++ b/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java
@@ -20,6 +20,7 @@
 import org.apache.calcite.avatica.util.DateTimeUtils;
 import org.apache.calcite.config.Lex;
 import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.jdbc.Driver;
 import org.apache.calcite.linq4j.Enumerable;
 import org.apache.calcite.linq4j.Linq4j;
@@ -44,6 +45,7 @@
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
@@ -61,6 +63,7 @@
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 
 import static org.apache.calcite.test.Matchers.isListOf;
@@ -72,6 +75,7 @@
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.hasToString;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -1102,4 +1106,58 @@ public static class DateColumnSchema {
     assertNotNull(statistic);
     assertThat(statistic.getRowCount(), is(2D));
   }
+
+  /** Test for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7712";>[CALCITE-7712]
+   * Enforce Input annotation on ReflectiveSchema.Factory operands</a>. */
+  @Test void testFactoryCreateWithAnnotatedClassOperand() {
+    ReflectiveSchema.Factory factory = new ReflectiveSchema.Factory();
+    SchemaPlus root = CalciteSchema.createRootSchema(false).plus();
+    Map<String, Object> operand =
+        ImmutableMap.of("class", 
"org.apache.calcite.test.ReflectiveSchemaTest$ValidClassOp");
+    assertNotNull(factory.create(root, "ignore", operand));
+  }
+
+  /** Test for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7712";>[CALCITE-7712]
+   * Enforce Input annotation on ReflectiveSchema.Factory operands</a>. The 
test ensures
+   * invalid classes are rejected with an informative exception.*/
+  @Test void testFactoryCreateWithInvalidClassOperandThrows() {
+    ReflectiveSchema.Factory factory = new ReflectiveSchema.Factory();
+    SchemaPlus root = CalciteSchema.createRootSchema(false).plus();
+    Map<String, Object> operand =
+        ImmutableMap.of("class", 
"org.apache.calcite.test.ReflectiveSchemaTest$InvalidClassOp");
+    IllegalArgumentException e =
+        assertThrows(IllegalArgumentException.class, () -> 
factory.create(root, "ignore", operand));
+    assertThat(e.getMessage(),
+        is("class org.apache.calcite.test.ReflectiveSchemaTest$InvalidClassOp 
is not annotated "
+            + "with @Input"));
+  }
+
+  /** Test for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7712";>[CALCITE-7712]
+   * Enforce Input annotation on ReflectiveSchema.Factory operands</a>. The 
test ensures
+   * invalid classes are not initialized.*/
+  @Test void 
testFactoryCreateWithInvalidClassOperandDoesNotTriggerInitializers() {
+    ReflectiveSchema.Factory factory = new ReflectiveSchema.Factory();
+    SchemaPlus root = CalciteSchema.createRootSchema(false).plus();
+    Map<String, Object> operand =
+        ImmutableMap.of("class", 
"org.apache.calcite.InvalidStaticInitializer");
+    IllegalArgumentException e =
+        assertThrows(IllegalArgumentException.class, () -> 
factory.create(root, "ignore", operand));
+    assertThat(e.getMessage(),
+        is("class org.apache.calcite.InvalidStaticInitializer is not annotated 
with @Input"));
+  }
+
+  /**
+   * A valid class operand for {@link ReflectiveSchema.Factory} with proper 
annotation.
+   */
+  @ReflectiveSchema.Input
+  public static class ValidClassOp {
+    public ValidClassOp() {}
+  }
+
+  /**
+   * An invalid class operand for {@link ReflectiveSchema.Factory} due to 
missing annotation.
+   */
+  public static class InvalidClassOp {
+    public InvalidClassOp() {}
+  }
 }
diff --git a/site/_docs/history.md b/site/_docs/history.md
index 9520e7d05f..3aba1fd075 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -54,6 +54,11 @@ #### Breaking Changes
   filter evaluation now run in Java, and the `arrow-gandiva` dependency is no
   longer included in the Arrow module or BOM.
 
+* [<a 
href="https://issues.apache.org/jira/browse/CALCITE-7712";>CALCITE-7712</a>]
+`ReflectiveSchema.Factory` requires the class operand to explicitly use the new
+`ReflectiveSchema.Input` annotation. If the annotation is not present the
+creation will fail with `IllegalArgumentException: class X is not annotated 
@Input`.
+
 * [<a 
href="https://issues.apache.org/jira/browse/CALCITE-7713";>CALCITE-7713</a>]
 Class loading from model files has been disabled by default. Any attempt to 
load
 classes from model files will lead to `SecurityException` unless an appropriate

Reply via email to