This is an automated email from the ASF dual-hosted git repository.
czy006 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git
The following commit(s) were added to refs/heads/master by this push:
new 1909b950b [AMORO-4350][catalog] Fix Hive view handling in table
management (#4351)
1909b950b is described below
commit 1909b950b0733fdfec5d5bc7532f3a4b6a86e5e7
Author: csurong <[email protected]>
AuthorDate: Tue Sep 15 21:20:52 2026 +0800
[AMORO-4350][catalog] Fix Hive view handling in table management (#4351)
* Fix Hive view filtering in table management
* Fix Hive view type compatibility across versions
* Simplify Hive view lookup and avoid redundant metastore calls
---------
Co-authored-by: caosurong <surong.cao>
Co-authored-by: ConradJam <[email protected]>
---
.../dashboard/controller/TableController.java | 10 +-
.../main/java/org/apache/amoro/hive/HMSClient.java | 12 ++
.../java/org/apache/amoro/hive/HMSClientImpl.java | 7 +
.../org/apache/amoro/hive/HiveTableTypeUtil.java | 89 +++++++++++++
.../test/java/org/apache/amoro/hive/TestHMS.java | 36 +++++
.../apache/amoro/hive/TestHiveTableTypeUtil.java | 80 +++++++++++
.../amoro/formats/iceberg/IcebergCatalog.java | 65 ++++++++-
.../formats/iceberg/IcebergCatalogFactory.java | 2 +-
.../amoro/formats/iceberg/TestIcebergCatalog.java | 72 ++++++++++
.../amoro/hive/catalog/MixedHiveCatalog.java | 20 ++-
.../org/apache/amoro/hive/utils/HiveTableUtil.java | 39 +++++-
.../amoro/hive/catalog/TestMixedHiveCatalog.java | 22 +++
.../hive/formats/TestIcebergHiveAmoroCatalog.java | 54 +++++++-
.../apache/amoro/hive/utils/TestHiveTableUtil.java | 147 +++++++++++++++++++++
14 files changed, 641 insertions(+), 14 deletions(-)
diff --git
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
index 772e8d0dd..087acdbc5 100644
---
a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
+++
b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java
@@ -199,7 +199,12 @@ public class TableController {
TableIdentifier tableIdentifier = TableIdentifier.of(catalog, db, table);
HiveTableInfo hiveTableInfo;
- Table hiveTable = HiveTableUtil.loadHmsTable(hmsClientPool,
tableIdentifier);
+ Table hiveTable = HiveTableUtil.loadPhysicalHmsTable(hmsClientPool,
tableIdentifier);
+ Preconditions.checkState(
+ hiveTable.getSd() != null,
+ "Hive table %s.%s does not have a storage descriptor",
+ db,
+ table);
List<AMSColumnInfo> schema =
transformHiveSchemaToAMSColumnInfo(hiveTable.getSd().getCols());
List<AMSColumnInfo> partitionColumnInfos =
transformHiveSchemaToAMSColumnInfo(hiveTable.getPartitionKeys());
@@ -727,6 +732,9 @@ public class TableController {
}
private List<AMSColumnInfo>
transformHiveSchemaToAMSColumnInfo(List<FieldSchema> fields) {
+ if (fields == null) {
+ return Collections.emptyList();
+ }
return fields.stream()
.map(
f -> {
diff --git a/amoro-common/src/main/java/org/apache/amoro/hive/HMSClient.java
b/amoro-common/src/main/java/org/apache/amoro/hive/HMSClient.java
index 140324abc..abb2aa92d 100644
--- a/amoro-common/src/main/java/org/apache/amoro/hive/HMSClient.java
+++ b/amoro-common/src/main/java/org/apache/amoro/hive/HMSClient.java
@@ -24,6 +24,7 @@ import
org.apache.hadoop.hive.metastore.api.EnvironmentContext;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.metastore.api.Partition;
import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
import org.apache.thrift.TException;
import java.lang.reflect.InvocationTargetException;
@@ -88,4 +89,15 @@ public interface HMSClient {
InvocationTargetException, ClassNotFoundException;
List<Table> getTableObjectsByName(String dbName, List<String> tableNames)
throws TException;
+
+ /**
+ * Returns lightweight table metadata matching the database, table, and
table type patterns.
+ *
+ * @param databasePattern Hive database name or pattern
+ * @param tablePattern Hive table name or pattern
+ * @param tableTypes Hive table types to include
+ * @return matching lightweight table metadata
+ */
+ List<TableMeta> getTableMeta(String databasePattern, String tablePattern,
List<String> tableTypes)
+ throws TException;
}
diff --git
a/amoro-common/src/main/java/org/apache/amoro/hive/HMSClientImpl.java
b/amoro-common/src/main/java/org/apache/amoro/hive/HMSClientImpl.java
index 85ddb6833..a1d49d014 100644
--- a/amoro-common/src/main/java/org/apache/amoro/hive/HMSClientImpl.java
+++ b/amoro-common/src/main/java/org/apache/amoro/hive/HMSClientImpl.java
@@ -26,6 +26,7 @@ import
org.apache.hadoop.hive.metastore.api.EnvironmentContext;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.metastore.api.Partition;
import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
import org.apache.thrift.TException;
import java.util.List;
@@ -187,4 +188,10 @@ public class HMSClientImpl implements HMSClient {
throws TException {
return getClient().getTableObjectsByName(dbName, tableNames);
}
+
+ @Override
+ public List<TableMeta> getTableMeta(
+ String databasePattern, String tablePattern, List<String> tableTypes)
throws TException {
+ return getClient().getTableMeta(databasePattern, tablePattern, tableTypes);
+ }
}
diff --git
a/amoro-common/src/main/java/org/apache/amoro/hive/HiveTableTypeUtil.java
b/amoro-common/src/main/java/org/apache/amoro/hive/HiveTableTypeUtil.java
new file mode 100644
index 000000000..38e4f8274
--- /dev/null
+++ b/amoro-common/src/main/java/org/apache/amoro/hive/HiveTableTypeUtil.java
@@ -0,0 +1,89 @@
+/*
+ * 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.amoro.hive;
+
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
+import org.apache.thrift.TException;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Utilities for classifying Hive Metastore table objects. */
+public final class HiveTableTypeUtil {
+
+ private static final String VIRTUAL_VIEW_TYPE = "VIRTUAL_VIEW";
+ private static final String MATERIALIZED_VIEW_TYPE = "MATERIALIZED_VIEW";
+ private static final List<String> VIEW_TYPES =
+ Collections.unmodifiableList(Arrays.asList(VIRTUAL_VIEW_TYPE,
MATERIALIZED_VIEW_TYPE));
+
+ private HiveTableTypeUtil() {}
+
+ /** Returns the Hive table type names representing virtual or materialized
views. */
+ public static List<String> viewTypes() {
+ return VIEW_TYPES;
+ }
+
+ /** Returns whether the Hive table is a virtual or materialized view. */
+ public static boolean isView(Table table) {
+ return table != null && isViewType(table.getTableType());
+ }
+
+ /** Returns whether the lightweight Hive table metadata describes a view. */
+ public static boolean isView(TableMeta table) {
+ return table != null && isViewType(table.getTableType());
+ }
+
+ /**
+ * Returns the normalized names of Hive views among the candidate tables.
+ *
+ * @param client Hive Metastore client
+ * @param database database containing the candidate tables
+ * @param candidateTableNames table names to inspect
+ * @return lowercase names of virtual and materialized views
+ */
+ public static Set<String> listViewNames(
+ HMSClient client, String database, List<String> candidateTableNames)
throws TException {
+ if (candidateTableNames == null || candidateTableNames.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ List<TableMeta> views = client.getTableMeta(database, "*", viewTypes());
+ if (views == null) {
+ throw new IllegalStateException(
+ "Hive Metastore returned null while loading table metadata from
database: " + database);
+ }
+ return views.stream()
+ .filter(HiveTableTypeUtil::isView)
+ .map(TableMeta::getTableName)
+ .filter(name -> name != null)
+ .map(name -> name.toLowerCase(Locale.ROOT))
+ .collect(Collectors.toCollection(HashSet::new));
+ }
+
+ private static boolean isViewType(String tableType) {
+ return VIRTUAL_VIEW_TYPE.equalsIgnoreCase(tableType)
+ || MATERIALIZED_VIEW_TYPE.equalsIgnoreCase(tableType);
+ }
+}
diff --git a/amoro-common/src/test/java/org/apache/amoro/hive/TestHMS.java
b/amoro-common/src/test/java/org/apache/amoro/hive/TestHMS.java
index b7f2efaf8..b61e5f9a5 100644
--- a/amoro-common/src/test/java/org/apache/amoro/hive/TestHMS.java
+++ b/amoro-common/src/test/java/org/apache/amoro/hive/TestHMS.java
@@ -21,6 +21,12 @@ package org.apache.amoro.hive;
import org.apache.amoro.SingletonResourceUtil;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
+import org.apache.hadoop.hive.metastore.TableType;
+import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hadoop.hive.metastore.api.SerDeInfo;
+import org.apache.hadoop.hive.metastore.api.StorageDescriptor;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.thrift.TException;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
@@ -28,6 +34,9 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
public class TestHMS extends ExternalResource {
private static final Logger LOG = LoggerFactory.getLogger(TestHMS.class);
@@ -71,6 +80,33 @@ public class TestHMS extends ExternalResource {
return mockHms.getClient();
}
+ public void createView(String database, String viewName) throws TException {
+ createView(database, viewName, Collections.emptyMap());
+ }
+
+ public void createView(String database, String viewName, Map<String, String>
parameters)
+ throws TException {
+ StorageDescriptor storageDescriptor = new StorageDescriptor();
+ storageDescriptor.setCols(
+ Collections.singletonList(new FieldSchema("id", "int", "view
column")));
+ storageDescriptor.setSerdeInfo(new SerDeInfo());
+ Table view =
+ new Table(
+ viewName,
+ database,
+ System.getProperty("user.name"),
+ (int) (System.currentTimeMillis() / 1000),
+ 0,
+ 0,
+ storageDescriptor,
+ Collections.emptyList(),
+ new HashMap<>(parameters),
+ "select 1 as id",
+ "select 1 as id",
+ TableType.VIRTUAL_VIEW.name());
+ getHiveClient().createTable(view);
+ }
+
public int getMetastorePort() {
return mockHms.getMetastorePort();
}
diff --git
a/amoro-common/src/test/java/org/apache/amoro/hive/TestHiveTableTypeUtil.java
b/amoro-common/src/test/java/org/apache/amoro/hive/TestHiveTableTypeUtil.java
new file mode 100644
index 000000000..50e55b77a
--- /dev/null
+++
b/amoro-common/src/test/java/org/apache/amoro/hive/TestHiveTableTypeUtil.java
@@ -0,0 +1,80 @@
+/*
+ * 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.amoro.hive;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.hadoop.hive.metastore.TableType;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+public class TestHiveTableTypeUtil {
+
+ @Test
+ public void testViewTypes() {
+
assertTrue(HiveTableTypeUtil.isView(tableWithType(TableType.VIRTUAL_VIEW.name())));
+ assertTrue(HiveTableTypeUtil.isView(tableWithType("MATERIALIZED_VIEW")));
+
assertFalse(HiveTableTypeUtil.isView(tableWithType(TableType.EXTERNAL_TABLE.name())));
+ assertFalse(HiveTableTypeUtil.isView(tableWithType(null)));
+ assertTrue(
+ HiveTableTypeUtil.isView(new TableMeta("database", "view",
TableType.VIRTUAL_VIEW.name())));
+ }
+
+ @Test
+ public void testListViewNames() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+ when(client.getTableMeta("database", "*", HiveTableTypeUtil.viewTypes()))
+ .thenReturn(
+ Arrays.asList(
+ new TableMeta("database", "Hive_View", "VIRTUAL_VIEW"),
+ new TableMeta("database", "Materialized_View",
"MATERIALIZED_VIEW"),
+ new TableMeta("database", "physical_table",
"EXTERNAL_TABLE")));
+
+ assertEquals(
+ new HashSet<>(Arrays.asList("hive_view", "materialized_view")),
+ HiveTableTypeUtil.listViewNames(
+ client, "database", Arrays.asList("Hive_View",
"Materialized_View", "physical_table")));
+ }
+
+ @Test
+ public void testListViewNamesSkipsEmptyCandidates() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+
+ assertTrue(
+ HiveTableTypeUtil.listViewNames(client, "database",
Collections.emptyList()).isEmpty());
+ assertTrue(HiveTableTypeUtil.listViewNames(client, "database",
null).isEmpty());
+ verifyNoInteractions(client);
+ }
+
+ private static Table tableWithType(String tableType) {
+ Table table = new Table();
+ table.setTableType(tableType);
+ return table;
+ }
+}
diff --git
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalog.java
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalog.java
index fbe1ff373..956f921c6 100644
---
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalog.java
+++
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalog.java
@@ -18,35 +18,69 @@
package org.apache.amoro.formats.iceberg;
+import static org.apache.iceberg.CatalogUtil.ICEBERG_CATALOG_TYPE;
+
import org.apache.amoro.AmoroTable;
import org.apache.amoro.FormatCatalog;
+import org.apache.amoro.hive.CachedHiveClientPool;
+import org.apache.amoro.hive.HMSClientPool;
+import org.apache.amoro.hive.HiveTableTypeUtil;
+import org.apache.amoro.properties.CatalogMetaProperties;
import org.apache.amoro.table.TableMetaStore;
import org.apache.amoro.utils.MixedFormatCatalogUtil;
+import org.apache.amoro.utils.PropertyUtil;
import org.apache.iceberg.Table;
import org.apache.iceberg.catalog.Catalog;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.SupportsNamespaces;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.NoSuchTableException;
+import org.apache.thrift.TException;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
public class IcebergCatalog implements FormatCatalog {
+ // Matches HiveCatalog without a compile-time dependency on
iceberg-hive-metastore.
+ private static final String LIST_ALL_TABLES = "list-all-tables";
+
private SupportsNamespaces asNamespaceCatalog;
private final Catalog icebergCatalog;
private final TableMetaStore metaStore;
private final Map<String, String> properties;
+ private final HMSClientPool hiveClientPool;
+ /**
+ * Creates an Iceberg format catalog.
+ *
+ * <p>For a Hive-backed catalog, {@code properties} must contain {@code
type=hive}. Production
+ * callers should normally use {@link IcebergCatalogFactory}, which supplies
the metastore type
+ * explicitly.
+ */
public IcebergCatalog(Catalog catalog, Map<String, String> properties,
TableMetaStore metaStore) {
+ this(catalog, properties.get(ICEBERG_CATALOG_TYPE), properties, metaStore);
+ }
+
+ IcebergCatalog(
+ Catalog catalog,
+ String metastoreType,
+ Map<String, String> properties,
+ TableMetaStore metaStore) {
this.icebergCatalog = MixedFormatCatalogUtil.buildCacheCatalog(catalog,
properties);
if (catalog instanceof SupportsNamespaces) {
this.asNamespaceCatalog = (SupportsNamespaces) catalog;
}
this.metaStore = metaStore;
this.properties = properties;
+ this.hiveClientPool =
+ CatalogMetaProperties.CATALOG_TYPE_HIVE.equalsIgnoreCase(metastoreType)
+ && PropertyUtil.propertyAsBoolean(properties, LIST_ALL_TABLES,
false)
+ ? new CachedHiveClientPool(metaStore, properties)
+ : null;
}
@Override
@@ -89,11 +123,32 @@ public class IcebergCatalog implements FormatCatalog {
@Override
public List<String> listTables(String database) {
- return metaStore.doAs(
- () ->
- icebergCatalog.listTables(Namespace.of(database)).stream()
- .map(TableIdentifier::name)
- .collect(Collectors.toList()));
+ List<String> tableNames =
+ metaStore.doAs(
+ () ->
+ icebergCatalog.listTables(Namespace.of(database)).stream()
+ .map(TableIdentifier::name)
+ .collect(Collectors.toList()));
+ if (hiveClientPool == null || tableNames.isEmpty()) {
+ return tableNames;
+ }
+
+ Set<String> viewNames = listHiveViewNames(database, tableNames);
+ return tableNames.stream()
+ .filter(tableName ->
!viewNames.contains(tableName.toLowerCase(Locale.ROOT)))
+ .collect(Collectors.toList());
+ }
+
+ private Set<String> listHiveViewNames(String database, List<String>
tableNames) {
+ try {
+ return hiveClientPool.run(
+ client -> HiveTableTypeUtil.listViewNames(client, database,
tableNames));
+ } catch (TException e) {
+ throw new RuntimeException("Failed to identify Hive views in database: "
+ database, e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while identifying Hive views",
e);
+ }
}
@Override
diff --git
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalogFactory.java
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalogFactory.java
index 432fa81b3..683ed00b2 100644
---
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalogFactory.java
+++
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/IcebergCatalogFactory.java
@@ -42,7 +42,7 @@ public class IcebergCatalogFactory implements
FormatCatalogFactory {
Catalog icebergCatalog =
CatalogUtil.buildIcebergCatalog(name, properties,
metaStore.getConfiguration());
- return new IcebergCatalog(icebergCatalog, properties, metaStore);
+ return new IcebergCatalog(icebergCatalog, metastoreType, properties,
metaStore);
}
@Override
diff --git
a/amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/TestIcebergCatalog.java
b/amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/TestIcebergCatalog.java
new file mode 100644
index 000000000..2926c2402
--- /dev/null
+++
b/amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/TestIcebergCatalog.java
@@ -0,0 +1,72 @@
+/*
+ * 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.amoro.formats.iceberg;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.amoro.properties.CatalogMetaProperties;
+import org.apache.amoro.table.TableMetaStore;
+import org.apache.iceberg.CatalogProperties;
+import org.apache.iceberg.catalog.Catalog;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public class TestIcebergCatalog {
+
+ @Test
+ public void testListTablesSkipsViewLookupByDefault() {
+ assertListTablesSkipsViewLookup(new HashMap<>());
+ }
+
+ @Test
+ public void testListTablesSkipsViewLookupWhenListAllTablesIsFalse() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put("list-all-tables", "false");
+ assertListTablesSkipsViewLookup(properties);
+ }
+
+ private void assertListTablesSkipsViewLookup(Map<String, String> properties)
{
+ properties.put(CatalogProperties.CACHE_ENABLED, "false");
+ Catalog catalog = mock(Catalog.class);
+ TableMetaStore metaStore = mock(TableMetaStore.class);
+ when(metaStore.doAs(any()))
+ .thenAnswer(invocation ->
invocation.<Callable<?>>getArgument(0).call());
+ when(catalog.listTables(Namespace.of("database")))
+ .thenReturn(Collections.singletonList(TableIdentifier.of("database",
"iceberg_table")));
+ IcebergCatalog amoroCatalog =
+ new IcebergCatalog(catalog, CatalogMetaProperties.CATALOG_TYPE_HIVE,
properties, metaStore);
+
+ assertEquals(Collections.singletonList("iceberg_table"),
amoroCatalog.listTables("database"));
+
+ verify(catalog).listTables(Namespace.of("database"));
+ verify(metaStore).doAs(any());
+ verifyNoMoreInteractions(catalog, metaStore);
+ }
+}
diff --git
a/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/catalog/MixedHiveCatalog.java
b/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/catalog/MixedHiveCatalog.java
index 6e43e0fb8..ae529e83e 100644
---
a/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/catalog/MixedHiveCatalog.java
+++
b/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/catalog/MixedHiveCatalog.java
@@ -33,6 +33,7 @@ import org.apache.amoro.api.TableMeta;
import org.apache.amoro.hive.CachedHiveClientPool;
import org.apache.amoro.hive.HMSClient;
import org.apache.amoro.hive.HMSClientPool;
+import org.apache.amoro.hive.HiveTableTypeUtil;
import org.apache.amoro.hive.utils.CompatibleHivePropertyUtil;
import org.apache.amoro.hive.utils.HiveSchemaUtil;
import org.apache.amoro.hive.utils.HiveTableUtil;
@@ -183,14 +184,21 @@ public class MixedHiveCatalog implements
MixedFormatCatalog {
throw new NoSuchTableException("load table failed %s.", identifier);
}
- Map<String, String> hiveParameters = hiveTable.getParameters();
+ if (HiveTableTypeUtil.isView(hiveTable)) {
+ throw new NoSuchTableException("%s is a Hive view, not a Mixed Hive
table.", identifier);
+ }
- String mixedTableRootLocation =
hiveParameters.get(MIXED_TABLE_ROOT_LOCATION);
+ Map<String, String> hiveParameters = hiveTable.getParameters();
+ String mixedTableRootLocation =
+ hiveParameters == null ? null :
hiveParameters.get(MIXED_TABLE_ROOT_LOCATION);
if (mixedTableRootLocation == null) {
// if hive location ends with /hive, then it's a mixed-hive table. we
need to remove /hive to
// get root location.
// if hive location doesn't end with /hive, then it's a pure-hive table.
we can use the
// location as root location.
+ if (hiveTable.getSd() == null ||
StringUtils.isBlank(hiveTable.getSd().getLocation())) {
+ throw new NoSuchTableException("table %s does not have a storage
location.", identifier);
+ }
String hiveRootLocation = hiveTable.getSd().getLocation();
if (hiveRootLocation.endsWith("/hive")) {
mixedTableRootLocation = hiveRootLocation.substring(0,
hiveRootLocation.length() - 5);
@@ -305,7 +313,8 @@ public class MixedHiveCatalog implements MixedFormatCatalog
{
hiveTables.stream()
.filter(
table ->
- table.getParameters() != null
+ !HiveTableTypeUtil.isView(table)
+ && table.getParameters() != null
&&
CompatibleHivePropertyUtil.propertyAsBoolean(
table.getParameters(),
HiveTableProperties.MIXED_TABLE_FLAG,
@@ -327,8 +336,11 @@ public class MixedHiveCatalog implements
MixedFormatCatalog {
});
} catch (NoSuchObjectException e) {
// pass
- } catch (TException | InterruptedException e) {
+ } catch (TException e) {
throw new RuntimeException("Failed to listTables of database :" +
database, e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while listing tables of
database: " + database, e);
}
return result;
}
diff --git
a/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/utils/HiveTableUtil.java
b/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/utils/HiveTableUtil.java
index 64eb8e172..2c427b328 100644
---
a/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/utils/HiveTableUtil.java
+++
b/amoro-format-mixed/amoro-mixed-hive/src/main/java/org/apache/amoro/hive/utils/HiveTableUtil.java
@@ -18,7 +18,9 @@
package org.apache.amoro.hive.utils;
+import org.apache.amoro.NoSuchTableException;
import org.apache.amoro.hive.HMSClientPool;
+import org.apache.amoro.hive.HiveTableTypeUtil;
import org.apache.amoro.properties.HiveTableProperties;
import org.apache.amoro.shade.guava32.com.google.common.collect.Maps;
import org.apache.amoro.table.TableIdentifier;
@@ -37,8 +39,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
public class HiveTableUtil {
@@ -61,6 +67,23 @@ public class HiveTableUtil {
}
}
+ /**
+ * Loads a physical Hive table and rejects missing tables and Hive views.
+ *
+ * @param hiveClient Hive Metastore client pool
+ * @param tableIdentifier table to load
+ * @return the physical Hive table
+ * @throws NoSuchTableException if the table is missing or is a Hive view
+ */
+ public static Table loadPhysicalHmsTable(
+ HMSClientPool hiveClient, TableIdentifier tableIdentifier) {
+ Table table = loadHmsTable(hiveClient, tableIdentifier);
+ if (table == null || HiveTableTypeUtil.isView(table)) {
+ throw new NoSuchTableException("Hive table does not exist: " +
tableIdentifier);
+ }
+ return table;
+ }
+
public static void persistTable(
HMSClientPool hiveClient, org.apache.hadoop.hive.metastore.api.Table
tbl) {
try {
@@ -161,7 +184,8 @@ public class HiveTableUtil {
}
/**
- * Gets all the tables in a database.
+ * Gets all physical tables in a database. Hive views are excluded because
they cannot be managed
+ * as Hive or Mixed Hive tables.
*
* @param hiveClient Hive client from MixedHiveCatalog
* @param database Hive database
@@ -169,7 +193,18 @@ public class HiveTableUtil {
*/
public static List<String> getAllHiveTables(HMSClientPool hiveClient, String
database) {
try {
- return hiveClient.run(client -> client.getAllTables(database));
+ return hiveClient.run(
+ client -> {
+ List<String> tableNames = client.getAllTables(database);
+ if (tableNames == null || tableNames.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Set<String> viewNames = HiveTableTypeUtil.listViewNames(client,
database, tableNames);
+ return tableNames.stream()
+ .filter(name ->
!viewNames.contains(name.toLowerCase(Locale.ROOT)))
+ .collect(Collectors.toList());
+ });
} catch (TException e) {
throw new RuntimeException("Failed to get tables of database " +
database, e);
} catch (InterruptedException e) {
diff --git
a/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/catalog/TestMixedHiveCatalog.java
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/catalog/TestMixedHiveCatalog.java
index 973e8b7c2..e73d1c16b 100644
---
a/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/catalog/TestMixedHiveCatalog.java
+++
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/catalog/TestMixedHiveCatalog.java
@@ -23,18 +23,22 @@ import static
org.apache.amoro.properties.HiveTableProperties.MIXED_TABLE_ROOT_L
import org.apache.amoro.BasicTableTestHelper;
import org.apache.amoro.TableFormat;
+import org.apache.amoro.TableTestHelper;
import org.apache.amoro.catalog.TestMixedCatalog;
import org.apache.amoro.hive.TestHMS;
import org.apache.amoro.table.MixedTable;
import org.apache.amoro.table.TableIdentifier;
import org.apache.iceberg.PartitionSpec;
import org.apache.iceberg.Table;
+import org.apache.iceberg.exceptions.NoSuchTableException;
import org.apache.thrift.TException;
import org.junit.Assert;
import org.junit.ClassRule;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.Collections;
import java.util.Map;
@RunWith(JUnit4.class)
@@ -59,6 +63,24 @@ public class TestMixedHiveCatalog extends TestMixedCatalog {
return IDENTIFY_SPEC;
}
+ @Test
+ public void testHiveViewIsNotLoadableAsMixedHiveTable() throws Exception {
+ String database = TableTestHelper.TEST_DB_NAME;
+ String viewName = "test_hive_view";
+ getMixedFormatCatalog().createDatabase(database);
+ TEST_HMS.createView(database, viewName,
Collections.singletonMap(MIXED_TABLE_FLAG, "true"));
+ TableIdentifier viewIdentifier =
+ TableIdentifier.of(getCatalogMeta().getCatalogName(), database,
viewName);
+
+ try {
+
Assert.assertFalse(getMixedFormatCatalog().listTables(database).contains(viewIdentifier));
+ Assert.assertThrows(
+ NoSuchTableException.class, () ->
getMixedFormatCatalog().loadTable(viewIdentifier));
+ } finally {
+ TEST_HMS.getHiveClient().dropTable(database, viewName, false, true);
+ }
+ }
+
private void validateMixedHiveTableProperties(TableIdentifier
tableIdentifier) throws TException {
String dbName = tableIdentifier.getDatabase();
String tbl = tableIdentifier.getTableName();
diff --git
a/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/formats/TestIcebergHiveAmoroCatalog.java
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/formats/TestIcebergHiveAmoroCatalog.java
index ba4290431..58adea435 100644
---
a/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/formats/TestIcebergHiveAmoroCatalog.java
+++
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/formats/TestIcebergHiveAmoroCatalog.java
@@ -18,14 +18,25 @@
package org.apache.amoro.hive.formats;
+import org.apache.amoro.FormatCatalog;
+import org.apache.amoro.NoSuchTableException;
import org.apache.amoro.formats.AmoroCatalogTestHelper;
import org.apache.amoro.formats.TestIcebergAmoroCatalog;
import org.apache.amoro.hive.TestHMS;
+import org.apache.iceberg.catalog.Catalog;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.hive.HiveCatalog;
+import org.junit.Assert;
import org.junit.ClassRule;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
@RunWith(Parameterized.class)
public class TestIcebergHiveAmoroCatalog extends TestIcebergAmoroCatalog {
@@ -38,7 +49,15 @@ public class TestIcebergHiveAmoroCatalog extends
TestIcebergAmoroCatalog {
@Parameterized.Parameters(name = "{0}")
public static Object[] parameters() {
- return new Object[] {IcebergHiveCatalogTestHelper.defaultHelper()};
+ return new Object[] {
+ IcebergHiveCatalogTestHelper.defaultHelper(),
+ new IcebergHiveCatalogTestHelper(
+ "test_iceberg_catalog_list_all_false",
+ new HashMap<>(Collections.singletonMap(HiveCatalog.LIST_ALL_TABLES,
"false"))),
+ new IcebergHiveCatalogTestHelper(
+ "test_iceberg_catalog_list_all_true",
+ new HashMap<>(Collections.singletonMap(HiveCatalog.LIST_ALL_TABLES,
"true")))
+ };
}
@Override
@@ -46,4 +65,37 @@ public class TestIcebergHiveAmoroCatalog extends
TestIcebergAmoroCatalog {
catalogTestHelper.initHiveConf(TEST_HMS.getHiveConf());
super.setupCatalog();
}
+
+ @Test
+ public void testListTablesExcludesHiveViews() throws Exception {
+ String database = "view_filter_db";
+ String tableName = "iceberg_table";
+ String viewName = "hive_view";
+ createDatabase(database);
+ createTable(database, tableName, new HashMap<>());
+ TEST_HMS.createView(database, viewName);
+
+ try {
+ List<TableIdentifier> unfilteredTables =
+ ((Catalog) originalCatalog).listTables(Namespace.of(database));
+ boolean listAllTables =
+ Boolean.parseBoolean(
+ catalogTestHelper
+ .getCatalogMeta()
+ .getCatalogProperties()
+ .getOrDefault(HiveCatalog.LIST_ALL_TABLES,
HiveCatalog.LIST_ALL_TABLES_DEFAULT));
+ Assert.assertEquals(
+ listAllTables,
+
unfilteredTables.stream().map(TableIdentifier::name).anyMatch(viewName::equals));
+
+ List<String> tableNames = ((FormatCatalog)
amoroCatalog).listTables(database);
+ Assert.assertTrue(tableNames.contains(tableName));
+ Assert.assertFalse(tableNames.contains(viewName));
+ Assert.assertFalse(amoroCatalog.tableExists(database, viewName));
+ Assert.assertThrows(
+ NoSuchTableException.class, () -> amoroCatalog.loadTable(database,
viewName));
+ } finally {
+ TEST_HMS.getHiveClient().dropTable(database, viewName, false, true);
+ }
+ }
}
diff --git
a/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/utils/TestHiveTableUtil.java
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/utils/TestHiveTableUtil.java
new file mode 100644
index 000000000..02f53a08b
--- /dev/null
+++
b/amoro-format-mixed/amoro-mixed-hive/src/test/java/org/apache/amoro/hive/utils/TestHiveTableUtil.java
@@ -0,0 +1,147 @@
+/*
+ * 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.amoro.hive.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.amoro.NoSuchTableException;
+import org.apache.amoro.client.ClientPool;
+import org.apache.amoro.hive.HMSClient;
+import org.apache.amoro.hive.HMSClientPool;
+import org.apache.amoro.table.TableIdentifier;
+import org.apache.hadoop.hive.metastore.TableType;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
+import org.apache.thrift.TException;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class TestHiveTableUtil {
+
+ @Test
+ public void testGetAllHiveTablesExcludesViews() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+
when(client.getAllTables("database")).thenReturn(Arrays.asList("physical_table",
"hive_view"));
+ when(client.getTableMeta(eq("database"), eq("*"), anyList()))
+ .thenReturn(
+ Collections.singletonList(
+ new TableMeta("database", "hive_view",
TableType.VIRTUAL_VIEW.name())));
+
+ List<String> tableNames =
+ HiveTableUtil.getAllHiveTables(new TestingHMSClientPool(client),
"database");
+
+ assertEquals(Collections.singletonList("physical_table"), tableNames);
+ }
+
+ @Test
+ public void testGetAllHiveTablesPropagatesTableMetaFailure() throws
Exception {
+ HMSClient client = mock(HMSClient.class);
+
when(client.getAllTables("database")).thenReturn(Arrays.asList("physical_table",
"hive_view"));
+ TException failure = new TException("Failed to get table metadata");
+ when(client.getTableMeta(eq("database"), eq("*"),
anyList())).thenThrow(failure);
+
+ RuntimeException exception =
+ assertThrows(
+ RuntimeException.class,
+ () -> HiveTableUtil.getAllHiveTables(new
TestingHMSClientPool(client), "database"));
+
+ assertSame(failure, exception.getCause());
+ verify(client, never()).getTableObjectsByName(eq("database"), anyList());
+ }
+
+ @Test
+ public void testGetAllHiveTablesRejectsNullTableMeta() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+
when(client.getAllTables("database")).thenReturn(Arrays.asList("physical_table",
"hive_view"));
+ when(client.getTableMeta(eq("database"), eq("*"),
anyList())).thenReturn(null);
+ IllegalStateException exception =
+ assertThrows(
+ IllegalStateException.class,
+ () -> HiveTableUtil.getAllHiveTables(new
TestingHMSClientPool(client), "database"));
+
+ assertEquals(
+ "Hive Metastore returned null while loading table metadata from
database: database",
+ exception.getMessage());
+ verify(client, never()).getTableObjectsByName(eq("database"), anyList());
+ }
+
+ @Test
+ public void testLoadPhysicalHmsTableRejectsView() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+ when(client.getTable("database", "hive_view"))
+ .thenReturn(table("hive_view", TableType.VIRTUAL_VIEW));
+ TableIdentifier identifier = TableIdentifier.of("catalog", "database",
"hive_view");
+
+ assertThrows(
+ NoSuchTableException.class,
+ () -> HiveTableUtil.loadPhysicalHmsTable(new
TestingHMSClientPool(client), identifier));
+ }
+
+ @Test
+ public void testLoadPhysicalHmsTable() throws Exception {
+ HMSClient client = mock(HMSClient.class);
+ Table physicalTable = table("physical_table", TableType.EXTERNAL_TABLE);
+ when(client.getTable("database",
"physical_table")).thenReturn(physicalTable);
+ TableIdentifier identifier = TableIdentifier.of("catalog", "database",
"physical_table");
+
+ assertEquals(
+ physicalTable,
+ HiveTableUtil.loadPhysicalHmsTable(new TestingHMSClientPool(client),
identifier));
+ }
+
+ private static Table table(String name, TableType tableType) {
+ Table table = new Table();
+ table.setDbName("database");
+ table.setTableName(name);
+ table.setTableType(tableType.name());
+ return table;
+ }
+
+ private static class TestingHMSClientPool implements HMSClientPool {
+
+ private final HMSClient client;
+
+ private TestingHMSClientPool(HMSClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public <R> R run(ClientPool.Action<R, HMSClient, TException> action)
+ throws TException, InterruptedException {
+ return action.run(client);
+ }
+
+ @Override
+ public <R> R run(ClientPool.Action<R, HMSClient, TException> action,
boolean retry)
+ throws TException, InterruptedException {
+ return action.run(client);
+ }
+ }
+}