This is an automated email from the ASF dual-hosted git repository.
dengzh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new 78f50fdaf36 HIVE-29452: Split get table from HMSHandler (#6427)
78f50fdaf36 is described below
commit 78f50fdaf364f34e84cb867d36fde22ef0870ac2
Author: dengzh <[email protected]>
AuthorDate: Thu Apr 16 06:39:14 2026 +0800
HIVE-29452: Split get table from HMSHandler (#6427)
---
.../hadoop/hive/ql/TestDatabaseTableDefault.java | 2 +-
.../java/org/apache/hadoop/hive/ql/QTestUtil.java | 30 +
.../apache/hadoop/hive/metastore/HMSHandler.java | 508 ++---------------
.../hadoop/hive/metastore/handler/BaseHandler.java | 62 +-
.../metastore/handler/GetPartitionsHandler.java | 28 +-
.../hive/metastore/handler/GetTableHandler.java | 623 +++++++++++++++++++++
6 files changed, 731 insertions(+), 522 deletions(-)
diff --git
a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/ql/TestDatabaseTableDefault.java
b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/ql/TestDatabaseTableDefault.java
index a64bba594f6..3c5ab869c05 100644
---
a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/ql/TestDatabaseTableDefault.java
+++
b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/ql/TestDatabaseTableDefault.java
@@ -98,6 +98,7 @@ public void setUp() throws Exception {
TestTxnDbUtil.prepDb(hiveConf);
SessionState.start(new SessionState(hiveConf));
+ MetastoreConf.setBoolVar(hiveConf, ConfVars.HIVE_IN_TEST, false);
d = DriverFactory.newDriver(hiveConf);
wh = new File(System.getProperty("java.io.tmpdir") + File.separator +
@@ -110,7 +111,6 @@ public void setUp() throws Exception {
MetastoreConf.setVar(hiveConf,
ConfVars.METASTORE_METADATA_TRANSFORMER_CLASS,
"org.apache.hadoop.hive.metastore.MetastoreDefaultTransformer");
- MetastoreConf.setBoolVar(hiveConf, ConfVars.HIVE_IN_TEST, false);
MetastoreConf.setVar(hiveConf, ConfVars.WAREHOUSE,
wh.getCanonicalPath());
MetastoreConf.setVar(hiveConf, ConfVars.WAREHOUSE_EXTERNAL,
ext_wh.getCanonicalPath());
MetastoreConf.setBoolVar(hiveConf,
MetastoreConf.ConfVars.CREATE_TABLES_AS_ACID, true);
diff --git a/itests/util/src/main/java/org/apache/hadoop/hive/ql/QTestUtil.java
b/itests/util/src/main/java/org/apache/hadoop/hive/ql/QTestUtil.java
index 61dbfdd633c..447102f91c4 100644
--- a/itests/util/src/main/java/org/apache/hadoop/hive/ql/QTestUtil.java
+++ b/itests/util/src/main/java/org/apache/hadoop/hive/ql/QTestUtil.java
@@ -55,6 +55,10 @@
import org.apache.hadoop.hive.common.io.QTestFetchConverter;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
+import org.apache.hadoop.hive.metastore.api.Database;
+import org.apache.hadoop.hive.metastore.api.DatabaseType;
+import org.apache.hadoop.hive.metastore.api.GetDatabaseObjectsRequest;
+import org.apache.hadoop.hive.metastore.api.GetDatabaseObjectsResponse;
import org.apache.hadoop.hive.ql.metadata.HiveMetaStoreClientWithLocalCache;
import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
import org.apache.hadoop.hive.ql.QTestMiniClusters.FsType;
@@ -355,6 +359,31 @@ public void clearUDFsCreatedDuringTests() throws Exception
{
}
}
+ public void clearConnectorsCreatedInTests() throws Exception {
+ if (System.getenv(QTEST_LEAVE_FILES) != null) {
+ return;
+ }
+ conf.set("hive.metastore.filter.hook",
"org.apache.hadoop.hive.metastore.DefaultMetaStoreFilterHookImpl");
+ db = Hive.get(conf);
+
+ GetDatabaseObjectsRequest request = new GetDatabaseObjectsRequest();
+ request.setPattern("*");
+ GetDatabaseObjectsResponse response =
db.getMSC().get_databases_req(request);
+ if (response != null && response.getDatabasesSize() > 0) {
+ for (Database database : response.getDatabases()) {
+ if (database.getType() == DatabaseType.REMOTE) {
+ db.dropDatabase(database.getName(), true, true);
+ }
+ }
+ }
+ List<String> connectors = db.getAllDataConnectorNames();
+ if (connectors != null) {
+ for (String connectorName : connectors) {
+ db.dropDataConnector(connectorName, true);
+ }
+ }
+ }
+
/**
* Clear out any side effects of running tests
*/
@@ -482,6 +511,7 @@ public void clearTestSideEffects() throws Exception {
Utilities.clearWorkMap(conf);
NotificationEventPoll.shutdown();
QueryResultsCache.cleanupInstance();
+ clearConnectorsCreatedInTests();
clearTablesCreatedDuringTests();
clearUDFsCreatedDuringTests();
clearKeysCreatedInTests();
diff --git
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandler.java
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandler.java
index 842b9390af3..5a5d7ac95df 100644
---
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandler.java
+++
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandler.java
@@ -18,13 +18,11 @@
package org.apache.hadoop.hive.metastore;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.hive.common.StatsSetupConst;
import org.apache.hadoop.hive.common.TableName;
import org.apache.hadoop.hive.common.repl.ReplConst;
import org.apache.hadoop.hive.metastore.api.*;
@@ -39,6 +37,7 @@
import org.apache.hadoop.hive.metastore.handler.BaseHandler;
import org.apache.hadoop.hive.metastore.handler.DropPartitionsHandler;
import org.apache.hadoop.hive.metastore.handler.GetPartitionsHandler;
+import org.apache.hadoop.hive.metastore.handler.GetTableHandler;
import org.apache.hadoop.hive.metastore.handler.PrivilegeHandler;
import org.apache.hadoop.hive.metastore.messaging.EventMessage.EventType;
import org.apache.hadoop.hive.metastore.metrics.PerfLogger;
@@ -76,7 +75,6 @@
import static
org.apache.hadoop.hive.metastore.ExceptionHandler.throwMetaException;
import static org.apache.hadoop.hive.metastore.Warehouse.DEFAULT_CATALOG_NAME;
import static org.apache.hadoop.hive.metastore.Warehouse.DEFAULT_DATABASE_NAME;
-import static
org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars.HIVE_IN_TEST;
import static
org.apache.hadoop.hive.metastore.utils.MetaStoreServerUtils.getPartValsFromName;
import static
org.apache.hadoop.hive.metastore.utils.MetaStoreServerUtils.isDbReplicationTarget;
import static org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.CAT_NAME;
@@ -91,7 +89,6 @@
*/
public class HMSHandler extends PrivilegeHandler {
public static final Logger LOG = LoggerFactory.getLogger(HMSHandler.class);
- private final boolean isInTest;
private StorageSchemaReader storageSchemaReader;
public static final String PARTITION_NUMBER_EXCEED_LIMIT_MSG =
@@ -134,7 +131,6 @@ public static String getIPAddress() {
public HMSHandler(String name, Configuration conf) {
super(name, conf);
- isInTest = MetastoreConf.getBoolVar(this.conf, HIVE_IN_TEST);
}
@Override
@@ -1406,7 +1402,7 @@ public TruncateTableResponse
truncate_table_req(TruncateTableRequest req)
Exception ex = null;
boolean success = false;
try {
- success = AbstractRequestHandler.offer(this, req).success();
+ success = AbstractRequestHandler.offer(this, req).success();
return new TruncateTableResponse();
} catch (Exception e) {
ex = e;
@@ -1420,149 +1416,34 @@ public TruncateTableResponse
truncate_table_req(TruncateTableRequest req)
}
@Override
- public List<ExtendedTableInfo> get_tables_ext(final GetTablesExtRequest req)
throws MetaException {
- List<String> tables = new ArrayList<String>();
- List<ExtendedTableInfo> ret = new ArrayList<ExtendedTableInfo>();
- String catalog = req.getCatalog();
- String database = req.getDatabase();
- String pattern = req.getTableNamePattern();
- List<String> processorCapabilities = req.getProcessorCapabilities();
- int limit = req.getLimit();
- String processorId = req.getProcessorIdentifier();
- List<Table> tObjects = new ArrayList<>();
-
- startTableFunction("get_tables_ext", catalog, database, pattern);
- Exception ex = null;
- try {
- tables = getMS().getTables(catalog, database, pattern, null, limit);
- LOG.debug("get_tables_ext:getTables() returned " + tables.size());
- tables = FilterUtils.filterTableNamesIfEnabled(isServerFilterEnabled,
filterHook,
- catalog, database, tables);
-
- if (tables.size() > 0) {
- tObjects = getMS().getTableObjectsByName(catalog, database, tables);
- LOG.debug("get_tables_ext:getTableObjectsByName() returned " +
tObjects.size());
- if (processorCapabilities == null || processorCapabilities.size() == 0
||
- processorCapabilities.contains("MANAGERAWMETADATA")) {
- LOG.info("Skipping translation for processor with " + processorId);
- } else {
- if (transformer != null) {
- Map<Table, List<String>> retMap = transformer.transform(tObjects,
processorCapabilities, processorId);
-
- for (Map.Entry<Table, List<String>> entry : retMap.entrySet()) {
- LOG.debug("Table " + entry.getKey().getTableName() + " requires
" + Arrays.toString((entry.getValue()).toArray()));
- ret.add(convertTableToExtendedTable(entry.getKey(),
entry.getValue(), req.getRequestedFields()));
- }
- } else {
- for (Table table : tObjects) {
- ret.add(convertTableToExtendedTable(table,
processorCapabilities, req.getRequestedFields()));
- }
- }
- }
- }
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("get_tables_ext", ret != null, ex);
- }
- return ret;
- }
-
- private ExtendedTableInfo convertTableToExtendedTable (Table table,
- List<String>
processorCapabilities, int mask) {
- ExtendedTableInfo extTable = new ExtendedTableInfo(table.getTableName());
- if ((mask & GetTablesExtRequestFields.ACCESS_TYPE.getValue()) ==
GetTablesExtRequestFields.ACCESS_TYPE.getValue()) {
- extTable.setAccessType(table.getAccessType());
- }
-
- if ((mask & GetTablesExtRequestFields.PROCESSOR_CAPABILITIES.getValue())
- == GetTablesExtRequestFields.PROCESSOR_CAPABILITIES.getValue()) {
-
extTable.setRequiredReadCapabilities(table.getRequiredReadCapabilities());
-
extTable.setRequiredWriteCapabilities(table.getRequiredWriteCapabilities());
- }
-
- return extTable;
+ public List<ExtendedTableInfo> get_tables_ext(final GetTablesExtRequest req)
throws TException {
+ req.setCatalog(req.isSetCatalog() ? req.getCatalog() :
getDefaultCatalog(conf));
+ return GetTableHandler.getTables(
+ () -> startTableFunction("get_tables_ext", req.getCatalog(),
req.getDatabase(), req.getTableNamePattern()),
+ this, req,
+ t -> endFunction("get_tables_ext", t.getLeft() != null, t.getRight()));
}
@Override
public GetTableResult get_table_req(GetTableRequest req) throws
MetaException,
NoSuchObjectException {
req.setCatName(req.isSetCatName() ? req.getCatName() :
getDefaultCatalog(conf));
- return new GetTableResult(getTableInternal(req));
- }
-
- /**
- * This function retrieves table from metastore. If getColumnStats flag is
true,
- * then engine should be specified so the table is retrieve with the column
stats
- * for that engine.
- */
- private Table getTableInternal(GetTableRequest getTableRequest) throws
MetaException, NoSuchObjectException {
-
- Preconditions.checkArgument(!getTableRequest.isGetColumnStats() ||
getTableRequest.getEngine() != null,
- "To retrieve column statistics with a table, engine parameter cannot
be null");
-
- if (isInTest) {
- assertClientHasCapability(getTableRequest.getCapabilities(),
ClientCapability.TEST_CAPABILITY, "Hive tests",
- "get_table_req");
- }
-
- Table t = null;
- startTableFunction("get_table", getTableRequest.getCatName(),
getTableRequest.getDbName(),
- getTableRequest.getTblName());
- Exception ex = null;
- try {
- t = get_table_core(getTableRequest);
- if (MetaStoreUtils.isInsertOnlyTableParam(t.getParameters())) {
- assertClientHasCapability(getTableRequest.getCapabilities(),
ClientCapability.INSERT_ONLY_TABLES,
- "insert-only tables", "get_table_req");
- }
-
- if (CollectionUtils.isEmpty(getTableRequest.getProcessorCapabilities())
|| getTableRequest
- .getProcessorCapabilities().contains("MANAGERAWMETADATA")) {
- LOG.info("Skipping translation for processor with " +
getTableRequest.getProcessorIdentifier());
- } else {
- if (transformer != null) {
- List<Table> tList = new ArrayList<>();
- tList.add(t);
- Map<Table, List<String>> ret = transformer
- .transform(tList, getTableRequest.getProcessorCapabilities(),
getTableRequest.getProcessorIdentifier());
- if (ret.size() > 1) {
- LOG.warn("Unexpected resultset size:" + ret.size());
- throw new MetaException("Unexpected result from metadata
transformer:return list size is " + ret.size());
- }
- t = ret.keySet().iterator().next();
- }
- }
-
- firePreEvent(new PreReadTableEvent(t, this));
- } catch (MetaException | NoSuchObjectException e) {
- ex = e;
- throw e;
- } finally {
- endFunction("get_table", t != null, ex, getTableRequest.getTblName());
- }
- return t;
+ return new GetTableResult(GetTableHandler.getTable(
+ () -> startTableFunction("get_table", req.getCatName(),
req.getDbName(), req.getTblName()),
+ this, req, false,
+ t -> endFunction("get_table", t.getLeft() != null, t.getRight(),
req.getTblName())));
}
@Override
public List<TableMeta> get_table_meta(String dbnames, String tblNames,
List<String> tblTypes)
- throws MetaException, NoSuchObjectException {
- List<TableMeta> t = null;
+ throws TException {
String[] parsedDbName = parseDbName(dbnames, conf);
- startTableFunction("get_table_metas", parsedDbName[CAT_NAME],
parsedDbName[DB_NAME], tblNames);
- Exception ex = null;
- try {
- t = getMS().getTableMeta(parsedDbName[CAT_NAME], parsedDbName[DB_NAME],
tblNames, tblTypes);
- t = FilterUtils.filterTableMetasIfEnabled(isServerFilterEnabled,
filterHook, t);
- t = filterReadableTables(parsedDbName[CAT_NAME], t);
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("get_table_metas", t != null, ex);
- }
- return t;
+ return GetTableHandler.getTables(
+ () -> startTableFunction("get_table_metas", parsedDbName[CAT_NAME],
parsedDbName[DB_NAME], tblNames),
+ this,
+ GetTableHandler.GetTableNamesRequest.forTableMeta(dbnames, conf)
+ .byType(tblTypes != null && !tblTypes.isEmpty()? String.join(",",
tblTypes) : null, tblNames),
+ t -> endFunction("get_table_metas", t.getLeft() != null, t.getRight(),
join(t.getLeft(), ",")));
}
/**
@@ -1572,45 +1453,7 @@ public List<TableMeta> get_table_meta(String dbnames,
String tblNames, List<Stri
*/
@Override
public Table get_table_core(GetTableRequest getTableRequest) throws
MetaException, NoSuchObjectException {
- Preconditions.checkArgument(!getTableRequest.isGetColumnStats() ||
getTableRequest.getEngine() != null,
- "To retrieve column statistics with a table, engine parameter cannot
be null");
- String catName = getTableRequest.getCatName();
- String dbName = getTableRequest.getDbName();
- String tblName = getTableRequest.getTblName();
- Database db = null;
- Table t = null;
- try {
- db = get_database_core(catName, dbName);
- } catch (Exception e) { /* appears exception is not thrown currently if db
doesnt exist */ }
-
- if (MetaStoreUtils.isDatabaseRemote(db)) {
- t =
DataConnectorProviderFactory.getDataConnectorProvider(db).getTable(tblName);
- if (t == null) {
- throw new NoSuchObjectException(TableName.getQualified(catName,
dbName, tblName) + " table not found");
- }
- t.setDbName(dbName);
- return t;
- }
-
- try {
- t = getMS().getTable(catName, dbName, tblName,
getTableRequest.getValidWriteIdList(), getTableRequest.getId());
- if (t == null) {
- throw new NoSuchObjectException(TableName.getQualified(catName,
dbName, tblName) + " table not found");
- }
-
- // If column statistics was requested and is valid fetch it.
- if (getTableRequest.isGetColumnStats()) {
- ColumnStatistics colStats = getMS().getTableColumnStatistics(catName,
dbName, tblName,
- StatsSetupConst.getColumnsHavingStats(t.getParameters()),
getTableRequest.getEngine(),
- getTableRequest.getValidWriteIdList());
- if (colStats != null) {
- t.setColStats(colStats);
- }
- }
- } catch (Exception e) {
- throwMetaException(e);
- }
- return t;
+ return GetTableHandler.getTable(null, this, getTableRequest, true, null);
}
/**
@@ -1622,134 +1465,15 @@ public Table get_table_core(GetTableRequest
getTableRequest) throws MetaExceptio
* are retrievable from the database specified by "dbnames."
* There is no guarantee of the order of the returned tables.
* If there are duplicate names, only one instance of the table will
be returned.
- * @throws MetaException
- * @throws InvalidOperationException
- * @throws UnknownDBException
+ * @throws TException
*/
-
@Override
public GetTablesResult get_table_objects_by_name_req(GetTablesRequest req)
throws TException {
- String catName = req.isSetCatName() ? req.getCatName() :
getDefaultCatalog(conf);
- if (isDatabaseRemote(req.getDbName())) {
- return new
GetTablesResult(getRemoteTableObjectsInternal(req.getDbName(),
req.getTblNames(), req.getTablesPattern()));
- }
- return new GetTablesResult(getTableObjectsInternal(catName,
req.getDbName(),
- req.getTblNames(), req.getCapabilities(), req.getProjectionSpec(),
req.getTablesPattern()));
- }
-
- private List<Table> filterTablesByName(List<Table> tables, List<String>
tableNames) {
- List<Table> filteredTables = new ArrayList<>();
- for (Table table : tables) {
- if (tableNames.contains(table.getTableName())) {
- filteredTables.add(table);
- }
- }
- return filteredTables;
- }
-
- private List<Table> getRemoteTableObjectsInternal(String dbname,
List<String> tableNames, String pattern) throws MetaException {
- String[] parsedDbName = parseDbName(dbname, conf);
- try {
- // retrieve tables from remote database
- Database db = get_database_core(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
- List<Table> tables =
DataConnectorProviderFactory.getDataConnectorProvider(db).getTables(null);
-
- // filtered out undesired tables
- if (tableNames != null) {
- tables = filterTablesByName(tables, tableNames);
- }
-
- // set remote tables' local hive database reference
- for (Table table : tables) {
- table.setDbName(dbname);
- }
-
- return FilterUtils.filterTablesIfEnabled(isServerFilterEnabled,
filterHook, tables);
- } catch (Exception e) {
- LOG.warn("Unexpected exception while getting table(s) in remote database
" + dbname , e);
- if (isInTest) {
- // ignore the exception
- return new ArrayList<Table>();
- } else {
- throw newMetaException(e);
- }
- }
- }
-
- private List<Table> getTableObjectsInternal(String catName, String dbName,
- List<String> tableNames,
- ClientCapabilities capabilities,
- GetProjectionsSpec
projectionsSpec, String tablePattern)
- throws MetaException, InvalidOperationException, UnknownDBException {
- if (isInTest) {
- assertClientHasCapability(capabilities, ClientCapability.TEST_CAPABILITY,
- "Hive tests", "get_table_objects_by_name_req");
- }
-
- if (projectionsSpec != null) {
- if (!projectionsSpec.isSetFieldList() &&
(projectionsSpec.isSetIncludeParamKeyPattern() ||
- projectionsSpec.isSetExcludeParamKeyPattern())) {
- throw new InvalidOperationException("Include and Exclude Param key are
not supported.");
- }
- }
-
- List<Table> tables = new ArrayList<>();
- startMultiTableFunction("get_multi_table", dbName, tableNames);
- Exception ex = null;
- int tableBatchSize = MetastoreConf.getIntVar(conf,
- ConfVars.BATCH_RETRIEVE_MAX);
-
- try {
- if (dbName == null || dbName.isEmpty()) {
- throw new UnknownDBException("DB name is null or empty");
- }
- RawStore ms = getMS();
- if(tablePattern != null){
- tables = ms.getTableObjectsByName(catName, dbName, tableNames,
projectionsSpec, tablePattern);
- }else {
- if (tableNames == null) {
- throw new InvalidOperationException(dbName + " cannot find null
tables");
- }
-
- // The list of table names could contain duplicates.
RawStore.getTableObjectsByName()
- // only guarantees returning no duplicate table objects in one batch.
If we need
- // to break into multiple batches, remove duplicates first.
- List<String> distinctTableNames = tableNames;
- if (distinctTableNames.size() > tableBatchSize) {
- List<String> lowercaseTableNames = new ArrayList<>();
- for (String tableName : tableNames) {
- lowercaseTableNames.add(normalizeIdentifier(tableName));
- }
- distinctTableNames = new ArrayList<>(new
HashSet<>(lowercaseTableNames));
- }
-
- int startIndex = 0;
- // Retrieve the tables from the metastore in batches. Some databases
like
- // Oracle cannot have over 1000 expressions in a in-list
- while (startIndex < distinctTableNames.size()) {
- int endIndex = Math.min(startIndex + tableBatchSize,
distinctTableNames.size());
- tables.addAll(ms.getTableObjectsByName(catName, dbName,
distinctTableNames.subList(
- startIndex, endIndex), projectionsSpec, tablePattern));
- startIndex = endIndex;
- }
- }
- for (Table t : tables) {
- if (t.getParameters() != null &&
MetaStoreUtils.isInsertOnlyTableParam(t.getParameters())) {
- assertClientHasCapability(capabilities,
ClientCapability.INSERT_ONLY_TABLES,
- "insert-only tables", "get_table_req");
- }
- }
-
- tables = FilterUtils.filterTablesIfEnabled(isServerFilterEnabled,
filterHook, tables);
- } catch (Exception e) {
- ex = e;
- throw handleException(e)
- .throwIfInstance(MetaException.class,
InvalidOperationException.class, UnknownDBException.class)
- .defaultMetaException();
- } finally {
- endFunction("get_multi_table", tables != null, ex, join(tableNames,
","));
- }
- return tables;
+ List<Table> tables = GetTableHandler.getTables(
+ () -> startMultiTableFunction("get_multi_table", req.getDbName(),
req.getTblNames()),
+ this, req,
+ t -> endFunction("get_multi_table", t.getLeft() != null, t.getRight(),
join(req.getTblNames(), ",")));
+ return new GetTablesResult(tables);
}
@Override
@@ -1757,54 +1481,14 @@ public void update_creation_metadata(String catName,
final String dbName, final
getMS().updateCreationMetadata(catName, dbName, tableName, cm);
}
- private void assertClientHasCapability(ClientCapabilities client,
- ClientCapability value, String what,
String call) throws MetaException {
- if (!doesClientHaveCapability(client, value)) {
- throw new MetaException("Your client does not appear to support " + what
+ ". To skip"
- + " capability checks, please set " +
ConfVars.CAPABILITY_CHECK.toString()
- + " to false. This setting can be set globally, or on the client for
the current"
- + " metastore session. Note that this may lead to incorrect results,
data loss,"
- + " undefined behavior, etc. if your client is actually
incompatible. You can also"
- + " specify custom client capabilities via " + call + " API.");
- }
- }
-
- private boolean doesClientHaveCapability(ClientCapabilities client,
ClientCapability value) {
- if (!MetastoreConf.getBoolVar(getConf(), ConfVars.CAPABILITY_CHECK)) {
- return true;
- }
- return (client != null && client.isSetValues() &&
client.getValues().contains(value));
- }
-
@Override
public List<String> get_table_names_by_filter(
final String dbName, final String filter, final short maxTables)
- throws MetaException, InvalidOperationException, UnknownDBException {
- List<String> tables = null;
- startFunction("get_table_names_by_filter", ": db = " + dbName + ", filter
= " + filter);
- Exception ex = null;
- String[] parsedDbName = parseDbName(dbName, conf);
- try {
- if (parsedDbName[CAT_NAME] == null || parsedDbName[CAT_NAME].isEmpty() ||
- parsedDbName[DB_NAME] == null || parsedDbName[DB_NAME].isEmpty()) {
- throw new UnknownDBException("DB name is null or empty");
- }
- if (filter == null) {
- throw new InvalidOperationException(filter + " cannot apply null
filter");
- }
- tables = getMS().listTableNamesByFilter(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME], filter, maxTables);
- tables = FilterUtils.filterTableNamesIfEnabled(
- isServerFilterEnabled, filterHook, parsedDbName[CAT_NAME],
parsedDbName[DB_NAME], tables);
-
- } catch (Exception e) {
- ex = e;
- throw handleException(e)
- .throwIfInstance(MetaException.class,
InvalidOperationException.class, UnknownDBException.class)
- .defaultMetaException();
- } finally {
- endFunction("get_table_names_by_filter", tables != null, ex,
join(tables, ","));
- }
- return tables;
+ throws TException {
+ return GetTableHandler.getTables(
+ () -> startFunction("get_table_names_by_filter", ": db = " + dbName +
", filter = " + filter),
+ this, GetTableHandler.GetTableNamesRequest.fromDatabase(dbName,
conf).byFilter(filter, maxTables),
+ t -> endFunction("get_table_names_by_filter", t.getLeft() != null,
t.getRight(), join(t.getLeft(), ",")));
}
@Override
@@ -2136,20 +1820,21 @@ public DropPartitionsResult drop_partitions_req(
@Override
public boolean drop_partition_req(final DropPartitionRequest
dropPartitionReq) throws TException {
String dbName = dropPartitionReq.getDbName();
- String catName = dropPartitionReq.getCatName();
+ String catName = dropPartitionReq.isSetCatName() ?
dropPartitionReq.getCatName() : getDefaultCatalog(conf);
String tbl_name = dropPartitionReq.getTblName();
List<String> part_vals = dropPartitionReq.getPartVals();
try {
Table t = getMS().getTable(catName, dbName, tbl_name, null);
if (t == null) {
- throw new InvalidObjectException(dbName + "." + tbl_name + " table not
found");
+ throw new NoSuchObjectException(dbName + "." + tbl_name + " table not
found");
}
List<String> partNames = new ArrayList<>();
if (part_vals == null || part_vals.isEmpty()) {
part_vals = getPartValsFromName(t, dropPartitionReq.getPartName());
}
partNames.add(Warehouse.makePartName(t.getPartitionKeys(), part_vals));
- LOG.info("drop_partition_req partition values: {}", part_vals);
+ LOG.info("drop_partition_req partition values: {}, table: {}", part_vals,
+ new TableName(catName, dbName, tbl_name));
RequestPartsSpec requestPartsSpec = RequestPartsSpec.names(partNames);
DropPartitionsRequest request = new DropPartitionsRequest(dbName,
tbl_name, requestPartsSpec);
request.setCatName(catName);
@@ -2289,7 +1974,7 @@ public PartitionValuesResponse
get_partition_values(PartitionValuesRequest reque
return resps.getFirst();
} catch (Exception e) {
ex = e;
- throw
handleException(e).throwIfInstance(MetaException.class).defaultMetaException();
+ throw handleException(e).defaultMetaException();
} finally {
endFunction("get_partition_values", ex == null, ex,
tableName.toString());
}
@@ -2474,86 +2159,19 @@ private void alter_table_core(String catName, String
dbname, String name, Table
@Override
public List<String> get_tables(final String dbname, final String pattern)
- throws MetaException {
- startFunction("get_tables", ": db=" + dbname + " pat=" + pattern);
-
- List<String> ret = null;
- Exception ex = null;
- String[] parsedDbName = parseDbName(dbname, conf);
- try {
- if (isDatabaseRemote(dbname)) {
- Database db = get_database_core(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
- return
DataConnectorProviderFactory.getDataConnectorProvider(db).getTableNames();
- }
- } catch (Exception e) {
- throw newMetaException(e);
- }
-
- try {
- ret = getMS().getTables(parsedDbName[CAT_NAME], parsedDbName[DB_NAME],
pattern);
- if(ret != null && !ret.isEmpty()) {
- List<Table> tableInfo = new ArrayList<>();
- tableInfo = getMS().getTableObjectsByName(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME], ret);
- tableInfo = FilterUtils.filterTablesIfEnabled(isServerFilterEnabled,
filterHook, tableInfo);// tableInfo object has the owner information of the
table which is being passed to FilterUtils.
- ret = new ArrayList<>();
- for (Table tbl : tableInfo) {
- ret.add(tbl.getTableName());
- }
- }
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("get_tables", ret != null, ex);
- }
- return ret;
+ throws TException {
+ return GetTableHandler.getTables(() -> startFunction("get_tables", ": db="
+ dbname + " pat=" + pattern),
+ this, GetTableHandler.GetTableNamesRequest.fromDatabase(dbname,
conf).byPattern(pattern),
+ t -> endFunction("get_tables", t.getLeft() != null, t.getRight()));
}
@Override
public List<String> get_tables_by_type(final String dbname, final String
pattern, final String tableType)
- throws MetaException {
- startFunction("get_tables_by_type", ": db=" + dbname + " pat=" + pattern +
",type=" + tableType);
-
- List<String> ret = null;
- Exception ex = null;
- String[] parsedDbName = parseDbName(dbname, conf);
- try {
- ret = getTablesByTypeCore(parsedDbName[CAT_NAME], parsedDbName[DB_NAME],
pattern, tableType);
- ret = FilterUtils.filterTableNamesIfEnabled(isServerFilterEnabled,
filterHook,
- parsedDbName[CAT_NAME], parsedDbName[DB_NAME], ret);
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("get_tables_by_type", ret != null, ex);
- }
- return ret;
- }
-
- private List<String> getTablesByTypeCore(final String catName, final String
dbname,
- final String pattern, final String
tableType) throws MetaException {
- startFunction("getTablesByTypeCore", ": catName=" + catName +
- ": db=" + dbname + " pat=" + pattern + ",type=" + tableType);
-
- List<String> ret = null;
- Exception ex = null;
- Database db = null;
- try {
- db = get_database_core(catName, dbname);
- if (MetaStoreUtils.isDatabaseRemote(db)) {
- return
DataConnectorProviderFactory.getDataConnectorProvider(db).getTableNames();
- }
- } catch (Exception e) { /* ignore */ }
-
- try {
- ret = getMS().getTables(catName, dbname, pattern,
TableType.valueOf(tableType), -1);
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("getTablesByTypeCore", ret != null, ex);
- }
- return ret;
+ throws TException {
+ return GetTableHandler.getTables(
+ () -> startFunction("get_tables_by_type", ": db=" + dbname + " pat=" +
pattern + ",type=" + tableType),
+ this, GetTableHandler.GetTableNamesRequest.fromDatabase(dbname,
conf).byType(tableType, pattern),
+ t -> endFunction("get_tables_by_type", t.getLeft() != null,
t.getRight()));
}
@Override
@@ -2594,35 +2212,19 @@ public List<String>
get_materialized_views_for_rewriting(final String dbname)
}
@Override
- public List<String> get_all_tables(final String dbname) throws MetaException
{
- startFunction("get_all_tables", ": db=" + dbname);
-
- List<String> ret = null;
- Exception ex = null;
- String[] parsedDbName = parseDbName(dbname, conf);
+ public List<String> get_all_tables(final String dbname) throws TException {
try {
- if (isDatabaseRemote(dbname)) {
- Database db = get_database_core(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
- return
DataConnectorProviderFactory.getDataConnectorProvider(db).getTableNames();
+ return GetTableHandler.getTables(() -> startFunction("get_all_tables",
": db=" + dbname), this,
+ GetTableHandler.GetTableNamesRequest.fromDatabase(dbname, conf),
+ t -> endFunction("get_all_tables", t.getLeft() != null,
t.getRight()));
+ } catch (UnknownDBException ude) {
+ String[] parsedDbName = parseDbName(dbname, conf);
+ if (StringUtils.isEmpty(parsedDbName[DB_NAME])) {
+ throw new MetaException(ude.getMessage());
}
- } catch (Exception e) { /* ignore */ }
-
- try {
- if (isServerFilterEnabled) {
- List<TableMeta> filteredTableMetas = get_table_meta(dbname, "*", null);
- ret = filteredTableMetas.stream()
- .map(TableMeta::getTableName)
- .collect(Collectors.toList());
- } else {
- ret = getMS().getAllTables(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
- }
- } catch (Exception e) {
- ex = e;
- throw newMetaException(e);
- } finally {
- endFunction("get_all_tables", ret != null, ex);
+ // should throw the exception instead? in our tests we return an empty
list if dbName is valid
+ return Collections.emptyList();
}
- return ret;
}
private List<FieldSchema> get_fields_with_environment_context_core(String
db, String tableName, final EnvironmentContext envContext)
diff --git
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java
index b91af16a35c..5c4f5d868ba 100644
---
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java
+++
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java
@@ -24,6 +24,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
import com.google.common.util.concurrent.Striped;
import javax.jdo.JDOException;
@@ -89,12 +90,10 @@
import org.apache.hadoop.hive.metastore.api.PrivilegeBag;
import org.apache.hadoop.hive.metastore.api.PrivilegeGrantInfo;
import org.apache.hadoop.hive.metastore.api.Role;
-import org.apache.hadoop.hive.metastore.api.TableMeta;
import org.apache.hadoop.hive.metastore.api.Type;
import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
import org.apache.hadoop.hive.metastore.events.ConfigChangeEvent;
import org.apache.hadoop.hive.metastore.events.PreEventContext;
-import org.apache.hadoop.hive.metastore.events.PreReadDatabaseEvent;
import org.apache.hadoop.hive.metastore.messaging.EventMessage;
import org.apache.hadoop.hive.metastore.metrics.Metrics;
import org.apache.hadoop.hive.metastore.metrics.MetricsConstants;
@@ -123,7 +122,6 @@
import static org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.CAT_NAME;
import static org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.DB_NAME;
import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.parseDbName;
-import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.prependCatalogToDbName;
/**
* This class serves as the super class for all handlers that implement the
IHMSHandler
@@ -878,36 +876,6 @@ public CmRecycleResponse cm_recycle(final CmRecycleRequest
request) throws MetaE
return new CmRecycleResponse();
}
- /**
- * filters out the table meta for which read database access is not granted
- * @param catName catalog name
- * @param tableMetas list of table metas
- * @return filtered list of table metas
- * @throws RuntimeException
- * @throws NoSuchObjectException
- */
- protected List<TableMeta> filterReadableTables(String catName,
List<TableMeta> tableMetas)
- throws RuntimeException, NoSuchObjectException {
- List<TableMeta> finalT = new ArrayList<>();
- Map<String, Boolean> databaseNames = new HashMap();
- for (TableMeta tableMeta : tableMetas) {
- String fullDbName = prependCatalogToDbName(catName,
tableMeta.getDbName(), conf);
- if (databaseNames.get(fullDbName) == null) {
- boolean isExecptionThrown = false;
- try {
- fireReadDatabasePreEvent(fullDbName);
- } catch (MetaException e) {
- isExecptionThrown = true;
- }
- databaseNames.put(fullDbName, isExecptionThrown);
- }
- if (!databaseNames.get(fullDbName)) {
- finalT.add(tableMeta);
- }
- }
- return finalT;
- }
-
@Override
public void setMetaConf(String key, String value) throws MetaException {
MetastoreConf.ConfVars confVar = MetastoreConf.getMetaConf(key);
@@ -981,27 +949,6 @@ public String getMetaConf(String key) throws MetaException
{
return getConf().get(key, confVar.getDefaultVal().toString());
}
- /**
- * Fire a pre-event for read database operation, if there are any
- * pre-event listeners registered
- */
- protected void fireReadDatabasePreEvent(final String name)
- throws MetaException, RuntimeException, NoSuchObjectException {
- if(preListeners.size() > 0) {
- String[] parsedDbName = parseDbName(name, conf);
- Database db = null;
- try {
- db = get_database_core(parsedDbName[CAT_NAME], parsedDbName[DB_NAME]);
- if (db == null) {
- throw new NoSuchObjectException("Database: " + name + " not found");
- }
- } catch(MetaException | NoSuchObjectException e) {
- throw new RuntimeException(e);
- }
- firePreEvent(new PreReadDatabaseEvent(db, this));
- }
- }
-
public void firePreEvent(PreEventContext event) throws MetaException {
for (MetaStorePreEventListener listener : preListeners) {
try {
@@ -1013,4 +960,11 @@ public void firePreEvent(PreEventContext event) throws
MetaException {
}
}
}
+
+ public void firePreEvent(Supplier<PreEventContext> supplier) throws
MetaException {
+ if (preListeners.isEmpty()) {
+ return;
+ }
+ firePreEvent(supplier.get());
+ }
}
diff --git
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetPartitionsHandler.java
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetPartitionsHandler.java
index 2dcb768e83e..4902ec3d8af 100644
---
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetPartitionsHandler.java
+++
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetPartitionsHandler.java
@@ -61,7 +61,7 @@
// Collect get partitions APIs together
@SuppressWarnings({"unchecked", "rawtypes"})
@RequestHandler(requestBody = GetPartitionsHandler.GetPartitionsRequest.class)
-public class GetPartitionsHandler<Req, T> extends
AbstractRequestHandler<GetPartitionsHandler.GetPartitionsRequest<Req>,
+public class GetPartitionsHandler<R, T> extends
AbstractRequestHandler<GetPartitionsHandler.GetPartitionsRequest<R>,
GetPartitionsHandler.GetPartitionsResult<T>> {
private static final Logger LOG =
LoggerFactory.getLogger(GetPartitionsHandler.class);
private static final String NO_FILTER_STRING = "";
@@ -99,7 +99,7 @@ protected void beforeExecute() throws TException, IOException
{
@Override
protected GetPartitionsResult execute() throws TException, IOException {
- Req req = request.getReq();
+ R req = request.getReq();
if (req instanceof PartitionValuesRequest pvq) {
return getPartitionValues(pvq);
} else if (req instanceof GetPartitionsByNamesRequest gpbr) {
@@ -113,7 +113,7 @@ protected GetPartitionsResult execute() throws TException,
IOException {
} else if (req instanceof GetPartitionsPsWithAuthRequest gpar) {
return getPartitionsByVals(gpar);
}
- throw new UnsupportedOperationException("Not yet implemented");
+ throw new UnsupportedOperationException(req + " not yet implemented");
}
private GetPartitionsResult
getPartitionsByVals(GetPartitionsPsWithAuthRequest gpar) throws TException {
@@ -341,23 +341,23 @@ public List<T> result() {
}
}
- public static class GetPartitionsRequest<Req> extends TAbstractBase {
+ public static class GetPartitionsRequest<R> extends TAbstractBase {
private final TableName tableName;
private final boolean fetchPartNames;
- private final Req req;
+ private final R req;
- public GetPartitionsRequest(Req req, TableName tableName,
+ public GetPartitionsRequest(R req, TableName tableName,
boolean fetchPartNames) {
this.tableName = tableName;
this.fetchPartNames = fetchPartNames;
this.req = req;
}
- public GetPartitionsRequest(Req req, TableName tableName) {
+ public GetPartitionsRequest(R req, TableName tableName) {
this(req, tableName, false);
}
- public Req getReq() {
+ public R getReq() {
return req;
}
@@ -424,16 +424,16 @@ public static <Req> List<Partition>
getPartitions(Consumer<TableName> preHook,
}
}
- public static <Req> GetPartitionsResult<Partition> getPartitionsResult(
+ public static <R> GetPartitionsResult<Partition> getPartitionsResult(
Consumer<TableName> preHook,
Consumer<Pair<GetPartitionsResult, Exception>> postHook,
- IHMSHandler handler, TableName tableName, Req req) throws TException {
+ IHMSHandler handler, TableName tableName, R req) throws TException {
GetPartitionsResult result = null;
Exception ex = null;
try {
GetPartitionsRequest getPartitionsRequest = new
GetPartitionsRequest(req, tableName);
preHook.accept(tableName);
- GetPartitionsHandler<Req, Partition> getPartsHandler =
+ GetPartitionsHandler<R, Partition> getPartsHandler =
AbstractRequestHandler.offer(handler, getPartitionsRequest);
result = getPartsHandler.getResult();
return result;
@@ -445,15 +445,15 @@ public static <Req> GetPartitionsResult<Partition>
getPartitionsResult(
}
}
- public static <Req> GetPartitionsResult<String>
getPartitionNames(Consumer<TableName> preExecutor,
+ public static <R> GetPartitionsResult<String>
getPartitionNames(Consumer<TableName> preExecutor,
Consumer<Pair<GetPartitionsResult, Exception>> postConsumer, IHMSHandler
handler, TableName tableName,
- Req req) throws TException {
+ R req) throws TException {
Exception ex = null;
GetPartitionsResult result = null;
try {
preExecutor.accept(tableName);
GetPartitionsRequest getPartitionsRequest = new
GetPartitionsRequest(req, tableName, true);
- GetPartitionsHandler<Req, String> getPartNamesHandler =
+ GetPartitionsHandler<R, String> getPartNamesHandler =
AbstractRequestHandler.offer(handler, getPartitionsRequest);
result = getPartNamesHandler.getResult();
return result;
diff --git
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetTableHandler.java
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetTableHandler.java
new file mode 100644
index 00000000000..6c69c693fd4
--- /dev/null
+++
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/GetTableHandler.java
@@ -0,0 +1,623 @@
+/*
+ * 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.hadoop.hive.metastore.handler;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.common.DatabaseName;
+import org.apache.hadoop.hive.common.StatsSetupConst;
+import org.apache.hadoop.hive.common.TableName;
+import org.apache.hadoop.hive.metastore.HMSHandler;
+import org.apache.hadoop.hive.metastore.IHMSHandler;
+import org.apache.hadoop.hive.metastore.IMetaStoreMetadataTransformer;
+import org.apache.hadoop.hive.metastore.MetaStoreFilterHook;
+import org.apache.hadoop.hive.metastore.RawStore;
+import org.apache.hadoop.hive.metastore.TableType;
+import org.apache.hadoop.hive.metastore.api.ClientCapabilities;
+import org.apache.hadoop.hive.metastore.api.ClientCapability;
+import org.apache.hadoop.hive.metastore.api.ColumnStatistics;
+import org.apache.hadoop.hive.metastore.api.Database;
+import org.apache.hadoop.hive.metastore.api.ExtendedTableInfo;
+import org.apache.hadoop.hive.metastore.api.GetProjectionsSpec;
+import org.apache.hadoop.hive.metastore.api.GetTableRequest;
+import org.apache.hadoop.hive.metastore.api.GetTablesExtRequest;
+import org.apache.hadoop.hive.metastore.api.GetTablesExtRequestFields;
+import org.apache.hadoop.hive.metastore.api.GetTablesRequest;
+import org.apache.hadoop.hive.metastore.api.InvalidOperationException;
+import org.apache.hadoop.hive.metastore.api.MetaException;
+import org.apache.hadoop.hive.metastore.api.NoSuchObjectException;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.apache.hadoop.hive.metastore.api.TableMeta;
+import org.apache.hadoop.hive.metastore.api.UnknownDBException;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import
org.apache.hadoop.hive.metastore.dataconnector.DataConnectorProviderFactory;
+import org.apache.hadoop.hive.metastore.events.PreEventContext;
+import org.apache.hadoop.hive.metastore.events.PreReadDatabaseEvent;
+import org.apache.hadoop.hive.metastore.events.PreReadTableEvent;
+import org.apache.hadoop.hive.metastore.utils.FilterUtils;
+import org.apache.hadoop.hive.metastore.utils.MetaStoreUtils;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static
org.apache.hadoop.hive.metastore.ExceptionHandler.handleException;
+import static
org.apache.hadoop.hive.metastore.ExceptionHandler.newMetaException;
+import static
org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars.HIVE_IN_TEST;
+import static org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.CAT_NAME;
+import static org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.DB_NAME;
+import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.getDefaultCatalog;
+import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.isDatabaseRemote;
+import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.parseDbName;
+import static
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.prependCatalogToDbName;
+import static
org.apache.hadoop.hive.metastore.utils.StringUtils.normalizeIdentifier;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+@RequestHandler(requestBody = GetTableHandler.GetTableReq.class)
+public class GetTableHandler<R, T> extends
+ AbstractRequestHandler<GetTableHandler.GetTableReq<R>,
GetTableHandler.GetTableResult<T>> {
+ private static final Logger LOG =
LoggerFactory.getLogger(GetTableHandler.class);
+ private RawStore ms;
+ private IMetaStoreMetadataTransformer transformer;
+ private MetaStoreFilterHook filterHook;
+ private Configuration conf;
+ private boolean isInTest;
+ GetTableHandler(IHMSHandler handler, GetTableReq request) {
+ super(handler, false, request);
+ }
+
+ @Override
+ protected void beforeExecute() throws TException, IOException {
+ this.ms = handler.getMS();
+ this.transformer = handler.getMetadataTransformer();
+ this.filterHook = handler.getMetaFilterHook();
+ this.conf = handler.getConf();
+
+ this.isInTest = MetastoreConf.getBoolVar(handler.getConf(), HIVE_IN_TEST);
+ }
+
+ @Override
+ protected GetTableResult execute() throws TException, IOException {
+ R req = request.getRequest();
+ if (req instanceof GetTablesExtRequest getTablesExtRequest) {
+ List<ExtendedTableInfo> extendedTableInfos =
getTablesExt(getTablesExtRequest);
+ return new GetTableResult(extendedTableInfos, true);
+ } else if (req instanceof GetTableRequest getTableRequest) {
+ Table table = request.rawTable ? getTableCore(getTableRequest) :
getTable(getTableRequest);
+ return new GetTableResult(List.of(table), true);
+ } else if (req instanceof GetTablesRequest getTablesRequest) {
+ List<Table> tables = getTableObjects(getTablesRequest);
+ return new GetTableResult(tables, true);
+ } else if (req instanceof GetTableNamesRequest getTableNames) {
+ return getTableNames.forTableMeta ? new
GetTableResult(getTableMeta(getTableNames), true) :
+ new GetTableResult(getTableNames(getTableNames), true);
+ }
+ throw new UnsupportedOperationException(req + " not yet implemented");
+ }
+
+ private List<ExtendedTableInfo> getTablesExt(GetTablesExtRequest req) throws
MetaException, UnknownDBException {
+ List<ExtendedTableInfo> ret = new ArrayList<ExtendedTableInfo>();
+ String pattern = req.getTableNamePattern();
+ List<String> processorCapabilities = req.getProcessorCapabilities();
+ int limit = req.getLimit();
+ String catalog = req.isSetCatalog() ? req.getCatalog() :
getDefaultCatalog(conf);
+ String database = req.getDatabase();
+ String processorId = req.getProcessorIdentifier();
+ List<String> tables = ms.getTables(catalog, database, pattern, null,
limit);
+ LOG.debug("get_tables_ext:getTables() returned {}", tables.size());
+ tables = FilterUtils.filterTableNamesIfEnabled(filterHook != null,
filterHook,
+ catalog, database, tables);
+ if (tables.isEmpty()) {
+ return ret;
+ }
+ List<Table> tObjects = ms.getTableObjectsByName(catalog, database, tables);
+ LOG.debug("get_tables_ext:getTableObjectsByName() returned {}",
tObjects.size());
+ if (processorCapabilities == null || processorCapabilities.isEmpty() ||
+ processorCapabilities.contains("MANAGERAWMETADATA")) {
+ LOG.info("Skipping translation for processor with {}", processorId);
+ } else {
+ if (transformer != null) {
+ Map<Table, List<String>> retMap = transformer.transform(tObjects,
processorCapabilities, processorId);
+ for (Map.Entry<Table, List<String>> entry : retMap.entrySet()) {
+ LOG.debug("Table " + entry.getKey().getTableName() + " requires " +
Arrays.toString((entry.getValue()).toArray()));
+ ret.add(convertTableToExtendedTable(entry.getKey(),
entry.getValue(), req.getRequestedFields()));
+ }
+ } else {
+ for (Table table : tObjects) {
+ ret.add(convertTableToExtendedTable(table, processorCapabilities,
req.getRequestedFields()));
+ }
+ }
+ }
+ return ret;
+ }
+
+ private ExtendedTableInfo convertTableToExtendedTable(Table table,
+ List<String> processorCapabilities, int mask) {
+ ExtendedTableInfo extTable = new ExtendedTableInfo(table.getTableName());
+ if ((mask & GetTablesExtRequestFields.ACCESS_TYPE.getValue()) ==
GetTablesExtRequestFields.ACCESS_TYPE.getValue()) {
+ extTable.setAccessType(table.getAccessType());
+ }
+
+ if ((mask & GetTablesExtRequestFields.PROCESSOR_CAPABILITIES.getValue())
+ == GetTablesExtRequestFields.PROCESSOR_CAPABILITIES.getValue()) {
+
extTable.setRequiredReadCapabilities(table.getRequiredReadCapabilities());
+
extTable.setRequiredWriteCapabilities(table.getRequiredWriteCapabilities());
+ }
+
+ return extTable;
+ }
+
+ /**
+ * This function retrieves table from metastore. If getColumnStats flag is
true,
+ * then engine should be specified so the table is retrieve with the column
stats
+ * for that engine.
+ */
+ private Table getTable(GetTableRequest getTableRequest) throws
MetaException, NoSuchObjectException {
+
+ Preconditions.checkArgument(!getTableRequest.isGetColumnStats() ||
getTableRequest.getEngine() != null,
+ "To retrieve column statistics with a table, engine parameter cannot
be null");
+
+ if (isInTest) {
+ assertClientHasCapability(getTableRequest.getCapabilities(),
ClientCapability.TEST_CAPABILITY, "Hive tests",
+ "get_table_req");
+ }
+
+ Table t = getTableCore(getTableRequest);
+ if (MetaStoreUtils.isInsertOnlyTableParam(t.getParameters())) {
+ assertClientHasCapability(getTableRequest.getCapabilities(),
ClientCapability.INSERT_ONLY_TABLES,
+ "insert-only tables", "get_table_req");
+ }
+
+ if (CollectionUtils.isEmpty(getTableRequest.getProcessorCapabilities()) ||
getTableRequest
+ .getProcessorCapabilities().contains("MANAGERAWMETADATA")) {
+ LOG.info("Skipping translation for processor with " +
getTableRequest.getProcessorIdentifier());
+ } else {
+ if (transformer != null) {
+ List<Table> tList = new ArrayList<>();
+ tList.add(t);
+ Map<Table, List<String>> ret = transformer
+ .transform(tList, getTableRequest.getProcessorCapabilities(),
getTableRequest.getProcessorIdentifier());
+ if (ret.size() > 1) {
+ LOG.warn("Unexpected resultset size:{}", ret.size());
+ throw new MetaException("Unexpected result from metadata
transformer:return list size is " + ret.size());
+ }
+ t = ret.keySet().iterator().next();
+ }
+ }
+
+ ((HMSHandler) handler).firePreEvent(new PreReadTableEvent(t, handler));
+ return t;
+ }
+
+ /**
+ * This function retrieves table from metastore. If getColumnStats flag is
true,
+ * then engine should be specified so the table is retrieve with the column
stats
+ * for that engine.
+ * This method is slightly different from {@link
#getTable(GetTableRequest)}, it retrieves the table stored as it is
+ * without transformation and access check, and is called inside the
Metastore service.
+ */
+ private Table getTableCore(GetTableRequest getTableRequest) throws
MetaException, NoSuchObjectException {
+ Preconditions.checkArgument(!getTableRequest.isGetColumnStats() ||
getTableRequest.getEngine() != null,
+ "To retrieve column statistics with a table, engine parameter cannot
be null");
+ String catName = getTableRequest.getCatName();
+ String dbName = getTableRequest.getDbName();
+ String tblName = getTableRequest.getTblName();
+ Database db = null;
+ Table t;
+ try {
+ db = handler.get_database_core(catName, dbName);
+ } catch (Exception e) { /* appears exception is not thrown currently if db
doesnt exist */ }
+
+ if (MetaStoreUtils.isDatabaseRemote(db)) {
+ t =
DataConnectorProviderFactory.getDataConnectorProvider(db).getTable(tblName);
+ if (t == null) {
+ throw new NoSuchObjectException(TableName.getQualified(catName,
dbName, tblName) + " table not found");
+ }
+ t.setDbName(dbName);
+ return t;
+ }
+
+ t = ms.getTable(catName, dbName, tblName,
getTableRequest.getValidWriteIdList(), getTableRequest.getId());
+ if (t == null) {
+ throw new NoSuchObjectException(TableName.getQualified(catName, dbName,
tblName) + " table not found");
+ }
+
+ // If column statistics was requested and is valid fetch it.
+ if (getTableRequest.isGetColumnStats()) {
+ ColumnStatistics colStats = ms.getTableColumnStatistics(catName, dbName,
tblName,
+ StatsSetupConst.getColumnsHavingStats(t.getParameters()),
getTableRequest.getEngine(),
+ getTableRequest.getValidWriteIdList());
+ if (colStats != null) {
+ t.setColStats(colStats);
+ }
+ }
+ return t;
+ }
+
+ private List<Table> getTableObjects(GetTablesRequest req) throws TException {
+ String catName = req.isSetCatName() ? req.getCatName() :
getDefaultCatalog(conf);
+ String dbName = req.getDbName();
+ if (dbName == null || dbName.isEmpty()) {
+ throw new UnknownDBException("DB name is null or empty");
+ }
+ try {
+ Database database = handler.get_database_core(catName, dbName);
+ if (isDatabaseRemote(database)) {
+ return getRemoteTableObjectsInternal(database, req.getTblNames(),
req.getTablesPattern());
+ }
+ } catch (NoSuchObjectException nse) {
+ // The caller consumes UnknownDBException other than
NoSuchObjectException
+ // in case database doesn't exist
+ throw new UnknownDBException("Could not find database " +
DatabaseName.getQualified(catName, dbName));
+ }
+ return getTableObjectsInternal(req);
+ }
+
+ private List<Table> filterTablesByName(List<Table> tables, List<String>
tableNames) {
+ List<Table> filteredTables = new ArrayList<>();
+ for (Table table : tables) {
+ if (tableNames.contains(table.getTableName())) {
+ filteredTables.add(table);
+ }
+ }
+ return filteredTables;
+ }
+
+ private List<Table> getRemoteTableObjectsInternal(Database db, List<String>
tableNames, String pattern) throws MetaException {
+ try {
+ List<Table> tables =
DataConnectorProviderFactory.getDataConnectorProvider(db).getTables(null);
+ // filtered out undesired tables
+ if (tableNames != null) {
+ tables = filterTablesByName(tables, tableNames);
+ }
+ // set remote tables' local hive database reference
+ for (Table table : tables) {
+ table.setDbName(db.getName());
+ }
+ return FilterUtils.filterTablesIfEnabled(filterHook != null, filterHook,
tables);
+ } catch (Exception e) {
+ LOG.warn("Unexpected exception while getting table(s) in remote database
" + db.getName() , e);
+ if (isInTest) {
+ // ignore the exception
+ return new ArrayList<Table>();
+ } else {
+ throw newMetaException(e);
+ }
+ }
+ }
+
+ private List<Table> getTableObjectsInternal(GetTablesRequest req)
+ throws MetaException, InvalidOperationException, UnknownDBException {
+ if (isInTest) {
+ assertClientHasCapability(req.getCapabilities(),
ClientCapability.TEST_CAPABILITY,
+ "Hive tests", "get_table_objects_by_name_req");
+ }
+
+ GetProjectionsSpec projectionsSpec = req.getProjectionSpec();
+ if (projectionsSpec != null) {
+ if (!projectionsSpec.isSetFieldList() &&
(projectionsSpec.isSetIncludeParamKeyPattern() ||
+ projectionsSpec.isSetExcludeParamKeyPattern())) {
+ throw new InvalidOperationException("Include and Exclude Param key are
not supported.");
+ }
+ }
+
+ String dbName = req.getDbName();
+ String catName = req.isSetCatName() ? req.getCatName() :
getDefaultCatalog(conf);
+ List<Table> tables = new ArrayList<>();
+ int tableBatchSize = MetastoreConf.getIntVar(conf,
MetastoreConf.ConfVars.BATCH_RETRIEVE_MAX);
+ List<String> tableNames = req.getTblNames();
+ if(req.getTablesPattern() != null) {
+ tables = ms.getTableObjectsByName(catName, dbName, tableNames,
projectionsSpec, req.getTablesPattern());
+ } else {
+ if (tableNames == null) {
+ throw new InvalidOperationException(dbName + " cannot find null
tables");
+ }
+
+ // The list of table names could contain duplicates.
RawStore.getTableObjectsByName()
+ // only guarantees returning no duplicate table objects in one batch. If
we need
+ // to break into multiple batches, remove duplicates first.
+ List<String> distinctTableNames = tableNames;
+ if (distinctTableNames.size() > tableBatchSize) {
+ List<String> lowercaseTableNames = new ArrayList<>();
+ for (String tableName : tableNames) {
+ lowercaseTableNames.add(normalizeIdentifier(tableName));
+ }
+ distinctTableNames = new ArrayList<>(new
HashSet<>(lowercaseTableNames));
+ }
+
+ int startIndex = 0;
+ // Retrieve the tables from the metastore in batches. Some databases like
+ // Oracle cannot have over 1000 expressions in a in-list
+ while (startIndex < distinctTableNames.size()) {
+ int endIndex = Math.min(startIndex + tableBatchSize,
distinctTableNames.size());
+ tables.addAll(ms.getTableObjectsByName(catName, dbName,
distinctTableNames.subList(
+ startIndex, endIndex), projectionsSpec, null));
+ startIndex = endIndex;
+ }
+ }
+ for (Table t : tables) {
+ if (t.getParameters() != null &&
MetaStoreUtils.isInsertOnlyTableParam(t.getParameters())) {
+ assertClientHasCapability(req.getCapabilities(),
ClientCapability.INSERT_ONLY_TABLES,
+ "insert-only tables", "get_table_req");
+ }
+ }
+
+ tables = FilterUtils.filterTablesIfEnabled(filterHook != null, filterHook,
tables);
+ return tables;
+ }
+
+ private void assertClientHasCapability(ClientCapabilities client,
+ ClientCapability value, String what, String call) throws MetaException {
+ if (!doesClientHaveCapability(client, value)) {
+ throw new MetaException("Your client does not appear to support " + what
+ ". To skip"
+ + " capability checks, please set " +
MetastoreConf.ConfVars.CAPABILITY_CHECK.toString()
+ + " to false. This setting can be set globally, or on the client for
the current"
+ + " metastore session. Note that this may lead to incorrect results,
data loss,"
+ + " undefined behavior, etc. if your client is actually
incompatible. You can also"
+ + " specify custom client capabilities via " + call + " API.");
+ }
+ }
+
+ private boolean doesClientHaveCapability(ClientCapabilities client,
ClientCapability value) {
+ if (!MetastoreConf.getBoolVar(conf,
MetastoreConf.ConfVars.CAPABILITY_CHECK)) {
+ return true;
+ }
+ return (client != null && client.isSetValues() &&
client.getValues().contains(value));
+ }
+
+ private List<TableMeta> getTableMeta(GetTableNamesRequest getNamesReq)
throws TException {
+ String catName = getNamesReq.catName;
+ String dbname = getNamesReq.dbName;
+ List<String> tblTypes = null;
+ if (getNamesReq.tableType != null) {
+ tblTypes = Arrays.asList(getNamesReq.tableType.split(","));
+ }
+ List<TableMeta> t = ms.getTableMeta(catName, dbname, getNamesReq.pattern,
tblTypes);
+ t = FilterUtils.filterTableMetasIfEnabled(filterHook != null, filterHook,
t);
+ return filterReadableTables(catName, t);
+ }
+
+ /**
+ * filters out the table meta for which read database access is not granted
+ * @param catName catalog name
+ * @param tableMetas list of table metas
+ * @return filtered list of table metas
+ * @throws RuntimeException
+ * @throws NoSuchObjectException
+ */
+ private List<TableMeta> filterReadableTables(String catName, List<TableMeta>
tableMetas)
+ throws RuntimeException, NoSuchObjectException {
+ List<TableMeta> finalT = new ArrayList<>();
+ Map<String, Boolean> databaseNames = new HashMap();
+ for (TableMeta tableMeta : tableMetas) {
+ String fullDbName = prependCatalogToDbName(catName,
tableMeta.getDbName(), conf);
+ if (databaseNames.get(fullDbName) == null) {
+ boolean isExecptionThrown = false;
+ try {
+ fireReadDatabasePreEvent(fullDbName);
+ } catch (MetaException e) {
+ isExecptionThrown = true;
+ }
+ databaseNames.put(fullDbName, isExecptionThrown);
+ }
+ if (!databaseNames.get(fullDbName)) {
+ finalT.add(tableMeta);
+ }
+ }
+ return finalT;
+ }
+
+ /**
+ * Fire a pre-event for read database operation, if there are any
+ * pre-event listeners registered
+ */
+ private void fireReadDatabasePreEvent(final String name)
+ throws MetaException, RuntimeException, NoSuchObjectException {
+ Supplier<PreEventContext> supplier = () -> {
+ String[] parsedDbName = parseDbName(name, conf);
+ Database db = null;
+ try {
+ db = handler.get_database_core(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
+ if (db == null) {
+ throw new NoSuchObjectException("Database: " + name + " not found");
+ }
+ } catch(MetaException | NoSuchObjectException e) {
+ throw new RuntimeException(e);
+ }
+ return new PreReadDatabaseEvent(db, handler);
+ };
+ ((HMSHandler) handler).firePreEvent(supplier);
+ }
+
+
+ private List<String> getTableNames(GetTableNamesRequest getNamesReq) throws
TException {
+ String catName = getNamesReq.catName;
+ String dbname = getNamesReq.dbName;
+ try {
+ Database database = handler.get_database_core(catName, dbname);
+ if (isDatabaseRemote(database)) {
+ List<String> ret =
DataConnectorProviderFactory.getDataConnectorProvider(database).getTableNames();
+ return FilterUtils.filterTableNamesIfEnabled(filterHook != null,
filterHook, catName, dbname, ret);
+ }
+ } catch (NoSuchObjectException nse) {
+ throw new UnknownDBException("Could not find database " +
DatabaseName.getQualified(catName, dbname));
+ }
+
+ List<String> names;
+ if (getNamesReq.filter != null) {
+ names = ms.listTableNamesByFilter(catName, dbname, getNamesReq.filter,
getNamesReq.limit);
+ } else if (getNamesReq.tableType != null) {
+ names = ms.getTables(catName, dbname, getNamesReq.pattern,
+ TableType.valueOf(getNamesReq.tableType), -1);
+ } else if (getNamesReq.pattern != null) {
+ names = ms.getTables(catName, dbname, getNamesReq.pattern);
+ } else {
+ names = ms.getAllTables(catName, dbname);
+ }
+ if (filterHook != null && !names.isEmpty()) {
+ String tables = String.join("|", names);
+ List<TableMeta> tableMetas = ms.getTableMeta(catName, dbname, tables,
null);
+ if (tableMetas == null || tableMetas.isEmpty()) {
+ return new ArrayList<>();
+ }
+ List<TableMeta> filteredTableMetas =
FilterUtils.filterTableMetasIfEnabled(filterHook != null, filterHook,
tableMetas);
+ return
filteredTableMetas.stream().map(TableMeta::getTableName).collect(Collectors.toList());
+ }
+ return names;
+ }
+
+ public static class GetTableReq<Req> extends TAbstractBase {
+ private final Req request;
+ private boolean rawTable;
+ public GetTableReq(Req req) {
+ this.request = req;
+ }
+ public Req getRequest() {
+ return request;
+ }
+ }
+
+ public static class GetTableNamesRequest extends TAbstractBase {
+ private final String catName;
+ private final String dbName;
+ private String filter;
+ private String pattern;
+ private String tableType;
+ private short limit;
+ private boolean forTableMeta;
+ private GetTableNamesRequest(String catalog, String database) {
+ this.catName = catalog;
+ this.dbName = database;
+ }
+ public static GetTableNamesRequest fromDatabase(String database,
Configuration configuration)
+ throws MetaException, UnknownDBException {
+ String[] parsedDbName = parseDbName(database, configuration);
+ if (parsedDbName[CAT_NAME] == null || parsedDbName[CAT_NAME].isEmpty() ||
+ parsedDbName[DB_NAME] == null || parsedDbName[DB_NAME].isEmpty()) {
+ throw new UnknownDBException("DB name is null or empty");
+ }
+ return new GetTableNamesRequest(parsedDbName[CAT_NAME],
parsedDbName[DB_NAME]);
+ }
+ public GetTableNamesRequest byFilter(String filter, short limit) throws
InvalidOperationException{
+ if (filter == null) {
+ throw new InvalidOperationException(filter + " cannot apply null
filter");
+ }
+ this.filter = filter;
+ this.limit = limit;
+ return this;
+ }
+
+ public GetTableNamesRequest byType(String tableType, String pattern) {
+ this.tableType = tableType;
+ this.pattern = pattern;
+ return this;
+ }
+
+ public GetTableNamesRequest byPattern(String pattern) {
+ return byType(null, pattern);
+ }
+
+ public static GetTableNamesRequest forTableMeta(String dbName,
Configuration configuration) {
+ // get_table_meta accepts null database name
+ String catName;
+ String database = null;
+ if (dbName == null) {
+ catName = getDefaultCatalog(configuration);
+ } else {
+ String[] parsedDbName = parseDbName(dbName, configuration);
+ catName = parsedDbName[CAT_NAME];
+ database = parsedDbName[DB_NAME];
+ }
+ GetTableNamesRequest req = new GetTableNamesRequest(catName, database);
+ req.forTableMeta = true;
+ return req;
+ }
+ }
+
+ public record GetTableResult<T>(List<T> result, boolean success) implements
Result {
+
+ }
+
+ public static <T, R> List<T> getTables(Runnable preHook,
+ IHMSHandler handler, R req,
+ Consumer<Pair<List<T>, Exception>> postHook) throws TException {
+ if (preHook != null) {
+ preHook.run();
+ }
+ List<T> tables = null;
+ Exception ex = null;
+ try {
+ GetTableReq<R> internalRequest = new GetTableReq<>(req);
+ GetTableHandler<GetTableRequest, T> getTablesHandler =
+ AbstractRequestHandler.offer(handler, internalRequest);
+ tables = getTablesHandler.getResult().result();
+ return tables;
+ } catch (Exception e) {
+ ex = e;
+ throw handleException(e).defaultTException();
+ } finally {
+ if (postHook != null) {
+ Pair<List<T>, Exception> result = Pair.of(tables, ex);
+ postHook.accept(result);
+ }
+ }
+ }
+
+ public static Table getTable(Runnable preHook,
+ IHMSHandler handler, GetTableRequest request, boolean rawTable,
+ Consumer<Pair<Table, Exception>> postHook)
+ throws NoSuchObjectException, MetaException {
+ if (preHook != null) {
+ preHook.run();
+ }
+ Table t = null;
+ Exception ex = null;
+ try {
+ GetTableReq<GetTableRequest> internalRequest = new
GetTableReq<>(request);
+ internalRequest.rawTable = rawTable;
+ GetTableHandler<GetTableRequest, Table> getTableHandler =
+ AbstractRequestHandler.offer(handler, internalRequest);
+ List<Table> tables = getTableHandler.getResult().result();
+ t = tables.getFirst();
+ return t;
+ } catch (Exception e) {
+ ex = e;
+ throw handleException(e).throwIfInstance(NoSuchObjectException.class,
MetaException.class)
+ .defaultMetaException();
+ } finally {
+ if (postHook != null) {
+ Pair<Table, Exception> result = Pair.of(t, ex);
+ postHook.accept(result);
+ }
+ }
+ }
+}