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

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


The following commit(s) were added to refs/heads/main by this push:
     new e783912f21 [#13213] improvement(flink-connector): Recognize catalog 
types from external BaseCatalogFactory jars (#13214)
e783912f21 is described below

commit e783912f21a3ab5676f89651285ddfcbdf201891
Author: Yuhui <[email protected]>
AuthorDate: Thu Sep 17 10:03:06 2026 +0800

    [#13213] improvement(flink-connector): Recognize catalog types from 
external BaseCatalogFactory jars (#13214)
    
    ### What changes were proposed in this pull request?
    
    `FactoryUtils.isGravitinoManagedCatalogType` now also accepts any type
    declared by a `BaseCatalogFactory` found through `ServiceLoader`, so a
    catalog jar outside the connector can contribute catalog types. Factory
    entries that fail to load are skipped.
    
    ### Why are the changes needed?
    
    External catalog implementations were discoverable by Flink but not
    recognized as Gravitino-managed by the connector.
    
    Fix: #13213
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added `TestFactoryUtils`.
---
 flink-connector/flink-common/build.gradle.kts      |   2 +
 .../flink/connector/utils/FactoryUtils.java        |  41 +++++++-
 .../utils/FakeExternalCatalogFactory.java          |  73 +++++++++++++
 .../flink/connector/utils/TestFactoryUtils.java    | 117 +++++++++++++++++++++
 .../org.apache.flink.table.factories.Factory       |  20 ++++
 5 files changed, 252 insertions(+), 1 deletion(-)

diff --git a/flink-connector/flink-common/build.gradle.kts 
b/flink-connector/flink-common/build.gradle.kts
index 6e121d52a7..9938408fad 100644
--- a/flink-connector/flink-common/build.gradle.kts
+++ b/flink-connector/flink-common/build.gradle.kts
@@ -208,6 +208,8 @@ val testJar by tasks.registering(Jar::class) {
   archiveClassifier.set("tests")
   archiveBaseName.set(artifactName)
   from(sourceSets["test"].output)
+  // The SPI fixture is only for this module's tests; keep it off the version 
modules' classpath.
+  exclude("META-INF/services/**")
 }
 
 configurations {
diff --git 
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/utils/FactoryUtils.java
 
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/utils/FactoryUtils.java
index 273da3f4ac..fc6fc9fb32 100644
--- 
a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/utils/FactoryUtils.java
+++ 
b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/utils/FactoryUtils.java
@@ -21,13 +21,18 @@ package org.apache.gravitino.flink.connector.utils;
 import static 
org.apache.flink.table.factories.FactoryUtil.validateFactoryOptions;
 import static 
org.apache.flink.table.factories.FactoryUtil.validateWatermarkOptions;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.flink.table.factories.CatalogFactory;
 import org.apache.flink.table.factories.Factory;
 import org.apache.flink.table.factories.FactoryUtil;
+import org.apache.gravitino.flink.connector.catalog.BaseCatalogFactory;
 import 
org.apache.gravitino.flink.connector.hive.GravitinoHiveCatalogFactoryOptions;
 import 
org.apache.gravitino.flink.connector.iceberg.GravitinoIcebergCatalogFactoryOptions;
 import 
org.apache.gravitino.flink.connector.jdbc.GravitinoJdbcCatalogFactoryOptions;
@@ -51,7 +56,41 @@ public class FactoryUtils {
           GravitinoPaimonCatalogFactoryOptions.IDENTIFIER);
 
   public static boolean isGravitinoManagedCatalogType(String type) {
-    return GRAVITINO_CATALOG_TYPES.contains(type);
+    return GRAVITINO_CATALOG_TYPES.contains(type) || 
isProvidedByCatalogFactory(type);
+  }
+
+  /**
+   * Whether a {@link BaseCatalogFactory} on the classpath declares the type. 
This lets a jar
+   * outside the connector contribute catalog types without registering them 
here.
+   */
+  private static boolean isProvidedByCatalogFactory(String type) {
+    if (type == null) {
+      return false;
+    }
+    return 
isProvidedByCatalogFactory(ServiceLoader.load(Factory.class).iterator(), type);
+  }
+
+  @VisibleForTesting
+  static boolean isProvidedByCatalogFactory(Iterator<Factory> factories, 
String type) {
+    while (true) {
+      try {
+        if (!factories.hasNext()) {
+          return false;
+        }
+        Factory factory = factories.next();
+        if (factory instanceof BaseCatalogFactory && 
type.equals(factory.factoryIdentifier())) {
+          return true;
+        }
+      } catch (ServiceConfigurationError | LinkageError | RuntimeException e) {
+        // A factory that cannot be loaded or fails to report its identifier 
cannot be the one
+        // asked for.
+        LOG.debug(
+            "Skip a {} entry that cannot be loaded while resolving catalog 
type '{}'.",
+            Factory.class.getName(),
+            type,
+            e);
+      }
+    }
   }
 
   /**
diff --git 
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/FakeExternalCatalogFactory.java
 
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/FakeExternalCatalogFactory.java
new file mode 100644
index 0000000000..664ceeda5d
--- /dev/null
+++ 
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/FakeExternalCatalogFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.gravitino.flink.connector.utils;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.flink.configuration.ConfigOption;
+import org.apache.flink.table.catalog.Catalog;
+import org.apache.gravitino.flink.connector.CatalogPropertiesConverter;
+import org.apache.gravitino.flink.connector.PartitionConverter;
+import org.apache.gravitino.flink.connector.catalog.BaseCatalogFactory;
+
+/** A catalog factory registered only on the test classpath, standing in for 
an external jar. */
+public class FakeExternalCatalogFactory implements BaseCatalogFactory {
+
+  public static final String IDENTIFIER = "gravitino-fake-external";
+
+  @Override
+  public String factoryIdentifier() {
+    return IDENTIFIER;
+  }
+
+  @Override
+  public Set<ConfigOption<?>> requiredOptions() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  public Set<ConfigOption<?>> optionalOptions() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  public Catalog createCatalog(Context context) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public String gravitinoCatalogProvider() {
+    return "fake-external";
+  }
+
+  @Override
+  public org.apache.gravitino.Catalog.Type gravitinoCatalogType() {
+    return org.apache.gravitino.Catalog.Type.RELATIONAL;
+  }
+
+  @Override
+  public CatalogPropertiesConverter catalogPropertiesConverter() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public PartitionConverter partitionConverter() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git 
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/TestFactoryUtils.java
 
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/TestFactoryUtils.java
new file mode 100644
index 0000000000..3bcea53ddb
--- /dev/null
+++ 
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/utils/TestFactoryUtils.java
@@ -0,0 +1,117 @@
+/*
+ * 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.gravitino.flink.connector.utils;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.ServiceConfigurationError;
+import org.apache.flink.table.factories.Factory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestFactoryUtils {
+
+  @Test
+  void testBuiltInTypesAreGravitinoManaged() {
+    
Assertions.assertTrue(FactoryUtils.isGravitinoManagedCatalogType("gravitino-hive"));
+    
Assertions.assertTrue(FactoryUtils.isGravitinoManagedCatalogType("gravitino-jdbc-mysql"));
+  }
+
+  @Test
+  void testExternalCatalogFactoryTypeIsGravitinoManaged() {
+    // Discovered through META-INF/services on the test classpath. Matching 
follows Flink's own
+    // factory discovery and is case-sensitive.
+    Assertions.assertTrue(
+        
FactoryUtils.isGravitinoManagedCatalogType(FakeExternalCatalogFactory.IDENTIFIER));
+    Assertions.assertFalse(
+        FactoryUtils.isGravitinoManagedCatalogType(
+            FakeExternalCatalogFactory.IDENTIFIER.toUpperCase()));
+  }
+
+  @Test
+  void testTypeWithoutCatalogFactoryIsNotGravitinoManaged() {
+    // No jar on this classpath registers a factory for these types.
+    
Assertions.assertFalse(FactoryUtils.isGravitinoManagedCatalogType("gravitino-jdbc-oracle"));
+    
Assertions.assertFalse(FactoryUtils.isGravitinoManagedCatalogType("gravitino-jdbc-custom"));
+    Assertions.assertFalse(FactoryUtils.isGravitinoManagedCatalogType(null));
+  }
+
+  @Test
+  void testFlinkNativeCatalogTypesAreNotGravitinoManaged() {
+    // These factories are on the test classpath but are not 
BaseCatalogFactory.
+    
Assertions.assertFalse(FactoryUtils.isGravitinoManagedCatalogType("generic_in_memory"));
+    Assertions.assertFalse(FactoryUtils.isGravitinoManagedCatalogType("hive"));
+  }
+
+  @Test
+  void testBrokenServiceEntriesAreSkipped() {
+    Iterator<Factory> factories =
+        new Iterator<Factory>() {
+          private final Iterator<Object> steps =
+              ImmutableList.of(
+                      new ServiceConfigurationError("missing provider"),
+                      new NoClassDefFoundError("missing/Dependency"),
+                      new IllegalStateException("factory init failed"),
+                      new FakeExternalCatalogFactory())
+                  .iterator();
+
+          @Override
+          public boolean hasNext() {
+            return steps.hasNext();
+          }
+
+          @Override
+          public Factory next() {
+            Object step = steps.next();
+            if (step instanceof Error) {
+              throw (Error) step;
+            }
+            if (step instanceof RuntimeException) {
+              throw (RuntimeException) step;
+            }
+            return (Factory) step;
+          }
+        };
+    Assertions.assertTrue(
+        FactoryUtils.isProvidedByCatalogFactory(factories, 
FakeExternalCatalogFactory.IDENTIFIER));
+  }
+
+  @Test
+  void testBrokenServiceEntryOnHasNextIsSkipped() {
+    Iterator<Factory> factories =
+        new Iterator<Factory>() {
+          private boolean failed = false;
+
+          @Override
+          public boolean hasNext() {
+            if (!failed) {
+              failed = true;
+              throw new ServiceConfigurationError("unreadable service file");
+            }
+            return false;
+          }
+
+          @Override
+          public Factory next() {
+            throw new IllegalStateException();
+          }
+        };
+    Assertions.assertFalse(FactoryUtils.isProvidedByCatalogFactory(factories, 
"any"));
+  }
+}
diff --git 
a/flink-connector/flink-common/src/test/resources/META-INF/services/org.apache.flink.table.factories.Factory
 
b/flink-connector/flink-common/src/test/resources/META-INF/services/org.apache.flink.table.factories.Factory
new file mode 100644
index 0000000000..d208e6d87f
--- /dev/null
+++ 
b/flink-connector/flink-common/src/test/resources/META-INF/services/org.apache.flink.table.factories.Factory
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+org.apache.gravitino.flink.connector.utils.FakeExternalCatalogFactory

Reply via email to