diff --git a/java/lance-namespace-hive2/src/main/java/org/lance/namespace/hive2/Hive2Namespace.java b/java/lance-namespace-hive2/src/main/java/org/lance/namespace/hive2/Hive2Namespace.java index f61ef01..f9933fe 100644 --- a/java/lance-namespace-hive2/src/main/java/org/lance/namespace/hive2/Hive2Namespace.java +++ b/java/lance-namespace-hive2/src/main/java/org/lance/namespace/hive2/Hive2Namespace.java @@ -58,6 +58,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -70,7 +71,7 @@ import static org.lance.namespace.hive2.Hive2ErrorType.TableAlreadyExists; import static org.lance.namespace.hive2.Hive2ErrorType.TableNotFound; -public class Hive2Namespace implements LanceNamespace { +public class Hive2Namespace implements LanceNamespace, Closeable { private static final Logger LOG = LoggerFactory.getLogger(Hive2Namespace.class); private Hive2ClientPool clientPool; @@ -687,4 +688,19 @@ private String getDefaultTableLocation(String namespaceName, String tableName) { return String.format( "%s/%s.db/%s", config.getRoot(), namespaceName.toLowerCase(), tableName.toLowerCase()); } + + /** + * Closes the underlying Hive Metastore client pool, releasing all pooled metastore connections. + * + *
As with any resource-holding namespace implementation, callers should close instances they + * no longer need so the pooled metastore client connections are released; this matters in + * particular for short-lived instances. Safe to call when {@link #initialize} was never invoked, + * and idempotent. + */ + @Override + public void close() { + if (clientPool != null) { + clientPool.close(); + } + } } diff --git a/java/lance-namespace-hive2/src/test/java/org/lance/namespace/hive2/TestHive2Namespace.java b/java/lance-namespace-hive2/src/test/java/org/lance/namespace/hive2/TestHive2Namespace.java index 27bbb82..d084619 100644 --- a/java/lance-namespace-hive2/src/test/java/org/lance/namespace/hive2/TestHive2Namespace.java +++ b/java/lance-namespace-hive2/src/test/java/org/lance/namespace/hive2/TestHive2Namespace.java @@ -276,4 +276,30 @@ public void testDropNamespaceFailMode() { assertThrows(LanceNamespaceException.class, () -> namespace.dropNamespace(dropRequest)); assertTrue(error.getMessage().contains("Database non_existent_db doesn't exist")); } + + @Test + public void testCloseReleasesClientPool() throws Exception { + // A freshly initialized namespace can be used and then closed (idempotently), releasing its + // client pool. + Hive2Namespace closeable = new Hive2Namespace(); + closeable.setHadoopConf(metastore.hiveConf()); + closeable.initialize(Maps.newHashMap(), allocator); + + // Exercise the metastore client so the pool has at least one live connection. + CreateNamespaceRequest nsRequest = new CreateNamespaceRequest(); + nsRequest.setId(Lists.list("close_test_db")); + nsRequest.setMode("Create"); + closeable.createNamespace(nsRequest); + + // close() must not throw, and must be idempotent. + closeable.close(); + closeable.close(); + } + + @Test + public void testCloseBeforeInitializeIsSafe() { + // close() before initialize() (clientPool == null) must be a no-op, not an NPE. + Hive2Namespace uninitialized = new Hive2Namespace(); + uninitialized.close(); + } } diff --git a/java/lance-namespace-hive3/src/main/java/org/lance/namespace/hive3/Hive3Namespace.java b/java/lance-namespace-hive3/src/main/java/org/lance/namespace/hive3/Hive3Namespace.java index 3a69698..103649e 100644 --- a/java/lance-namespace-hive3/src/main/java/org/lance/namespace/hive3/Hive3Namespace.java +++ b/java/lance-namespace-hive3/src/main/java/org/lance/namespace/hive3/Hive3Namespace.java @@ -60,13 +60,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -public class Hive3Namespace implements LanceNamespace { +public class Hive3Namespace implements LanceNamespace, Closeable { private static final Logger LOG = LoggerFactory.getLogger(Hive3Namespace.class); private Hive3ClientPool clientPool; @@ -801,4 +802,19 @@ private String getDefaultTableLocation(String catalogName, String dbName, String "%s/%s/%s.db/%s", config.getRoot(), catalogName.toLowerCase(), dbName.toLowerCase(), tableName.toLowerCase()); } + + /** + * Closes the underlying Hive Metastore client pool, releasing all pooled metastore connections. + * + *
As with any resource-holding namespace implementation, callers should close instances they
+ * no longer need so the pooled metastore client connections are released; this matters in
+ * particular for short-lived instances. Safe to call when {@link #initialize} was never invoked,
+ * and idempotent.
+ */
+ @Override
+ public void close() {
+ if (clientPool != null) {
+ clientPool.close();
+ }
+ }
}
diff --git a/java/lance-namespace-hive3/src/test/java/org/lance/namespace/hive3/TestHive3Namespace.java b/java/lance-namespace-hive3/src/test/java/org/lance/namespace/hive3/TestHive3Namespace.java
index e98376a..86bca58 100644
--- a/java/lance-namespace-hive3/src/test/java/org/lance/namespace/hive3/TestHive3Namespace.java
+++ b/java/lance-namespace-hive3/src/test/java/org/lance/namespace/hive3/TestHive3Namespace.java
@@ -387,4 +387,33 @@ public void testDropCatalogRestrictWithDatabases() {
assertTrue(error.getMessage().contains("is not empty"));
assertTrue(error.getMessage().contains("databases"));
}
+
+ @Test
+ public void testCloseReleasesClientPool() throws Exception {
+ // A freshly initialized namespace can be used and then closed (idempotently), releasing its
+ // client pool.
+ Hive3Namespace closeable = new Hive3Namespace();
+ closeable.setHadoopConf(metastore.hiveConf());
+ closeable.initialize(Maps.newHashMap(), allocator);
+
+ // Exercise the metastore client so the pool has at least one live connection.
+ CreateNamespaceRequest nsRequest = new CreateNamespaceRequest();
+ Map