You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2019/11/26 19:54:11 UTC

[asterixdb] branch master updated: [NO ISSUE] Make MetadataProvider extensible

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

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 311e46d  [NO ISSUE] Make MetadataProvider extensible
311e46d is described below

commit 311e46d4c3917daf503b03a011f0a5421ae52f11
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Tue Nov 19 13:46:47 2019 -0800

    [NO ISSUE] Make MetadataProvider extensible
    
    - user model changes: yes
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Allow product extensions to provide custom
      implementation of MetadataProvider
    - Modify 'async-deferred' testcases to
      request 'Clean-JSON' output
    
    Change-Id: I485073e6a9ec7e36083da6f50a8df63a37b2668b
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/4224
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Dmitry Lychagin <dm...@couchbase.com>
    Reviewed-by: Murtadha Hubail <mh...@apache.org>
---
 .../optimizer/rules/ConstantFoldingRule.java       |  2 +-
 .../api/http/server/ConnectorApiServlet.java       |  2 +-
 .../api/http/server/RebalanceApiServlet.java       |  2 +-
 .../app/active/ActiveEntityEventsListener.java     |  2 +-
 .../apache/asterix/app/active/RecoveryTask.java    |  2 +-
 .../apache/asterix/app/cc/CCExtensionManager.java  | 24 +++++++++++++++++-----
 .../asterix/app/translator/QueryTranslator.java    |  2 +-
 .../hyracks/bootstrap/GlobalRecoveryManager.java   |  2 +-
 .../org/apache/asterix/utils/ExtensionUtil.java    | 22 +++++++++++++++++++-
 .../api/http/servlet/ConnectorApiServletTest.java  |  4 ++--
 .../asterix/app/bootstrap/TestNodeController.java  | 12 +++++------
 .../org/apache/asterix/common/TestDataUtil.java    |  4 ++--
 .../test/active/ActiveEventsListenerTest.java      | 20 +++++++++---------
 .../asterix/test/active/ActiveStatsTest.java       |  2 +-
 .../ddl/SecondaryBTreeOperationsHelperTest.java    |  2 +-
 .../asterix/test/metadata/MetadataTxnTest.java     | 14 ++++++-------
 .../storage/IndexDropOperatorNodePushableTest.java |  2 +-
 .../async-deferred/AsyncDeferredQueries.xml        | 16 +++++++--------
 .../async-exhausted-result.3.json                  | 10 ---------
 .../async-exhausted-result.3.regexjson             | 15 ++++++++++++++
 .../async-repeated/async-repeated.3.json           | 10 ---------
 .../async-repeated/async-repeated.3.regexjson      | 15 ++++++++++++++
 .../async-repeated/async-repeated.4.json           | 10 ---------
 .../async-repeated/async-repeated.4.regexjson      | 15 ++++++++++++++
 .../async-running/async-running.4.json             |  1 -
 .../async-running/async-running.4.regexjson        |  6 ++++++
 .../results/async-deferred/async/async.3.json      | 10 ---------
 .../results/async-deferred/async/async.3.regexjson | 15 ++++++++++++++
 .../async-deferred/deferred/deferred.2.json        | 10 ---------
 .../async-deferred/deferred/deferred.2.regexjson   | 15 ++++++++++++++
 ...ensionManager.java => ICCExtensionManager.java} | 16 +++++++++------
 .../asterix/metadata/api/IMetadataExtension.java   | 10 +++++++++
 .../asterix/metadata/api/INCExtensionManager.java  |  2 --
 .../metadata/declared/MetadataProvider.java        | 13 ++++++++++--
 .../org/apache/asterix/om/utils/AdmNodeUtils.java  | 13 ++++++++++++
 .../java/org/apache/hyracks/util/NetworkUtil.java  | 12 +++++++++++
 36 files changed, 222 insertions(+), 112 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
index d8fa3d6..176a678 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ConstantFoldingRule.java
@@ -137,7 +137,7 @@ public class ConstantFoldingRule implements IAlgebraicRewriteRule {
     private static final IOperatorSchema[] _emptySchemas = new IOperatorSchema[] {};
 
     public ConstantFoldingRule(ICcApplicationContext appCtx) {
-        MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         jobGenCtx = new JobGenContext(null, metadataProvider, appCtx, SerializerDeserializerProvider.INSTANCE,
                 BinaryHashFunctionFactoryProvider.INSTANCE, BinaryHashFunctionFamilyProvider.INSTANCE,
                 BinaryComparatorFactoryProvider.INSTANCE, TypeTraitProvider.INSTANCE, BinaryBooleanInspector.FACTORY,
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
index 2a08511..2fafcfc 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
@@ -93,7 +93,7 @@ public class ConnectorApiServlet extends AbstractServlet {
             MetadataManager.INSTANCE.init();
             MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
             // Retrieves file splits of the dataset.
-            MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+            MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
             try {
                 metadataProvider.setMetadataTxnContext(mdTxnCtx);
                 Dataset dataset = metadataProvider.findDataset(dataverseName, datasetName);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
index 02d75f9..62b96bc 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
@@ -246,7 +246,7 @@ public class RebalanceApiServlet extends AbstractServlet {
     private void rebalanceDataset(DataverseName dataverseName, String datasetName, String[] targetNodes)
             throws Exception {
         IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
-        MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         try {
             ActiveNotificationHandler activeNotificationHandler =
                     (ActiveNotificationHandler) appCtx.getActiveNotificationHandler();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/ActiveEntityEventsListener.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/ActiveEntityEventsListener.java
index 882afc5..55022f9 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/ActiveEntityEventsListener.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/ActiveEntityEventsListener.java
@@ -113,7 +113,7 @@ public abstract class ActiveEntityEventsListener implements IActiveEntityControl
         this.statementExecutor = statementExecutor;
         this.appCtx = appCtx;
         this.clusterStateManager = appCtx.getClusterStateManager();
-        this.metadataProvider = new MetadataProvider(appCtx, null);
+        this.metadataProvider = MetadataProvider.create(appCtx, null);
         this.hcc = hcc;
         this.entityId = entityId;
         this.datasets = datasets;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/RecoveryTask.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/RecoveryTask.java
index 6efda9f..0ee41cd 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/RecoveryTask.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/active/RecoveryTask.java
@@ -52,7 +52,7 @@ public class RecoveryTask {
             IRetryPolicyFactory retryPolicyFactory) {
         this.listener = listener;
         this.retryPolicyFactory = retryPolicyFactory;
-        this.metadataProvider = new MetadataProvider(appCtx, null);
+        this.metadataProvider = MetadataProvider.create(appCtx, null);
         this.clusterStateManager = appCtx.getClusterStateManager();
     }
 
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/cc/CCExtensionManager.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/cc/CCExtensionManager.java
index 7f632f0..e3c177e 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/cc/CCExtensionManager.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/cc/CCExtensionManager.java
@@ -22,6 +22,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
 
 import org.apache.asterix.algebra.base.ILangExtension;
 import org.apache.asterix.algebra.base.ILangExtension.Language;
@@ -31,19 +32,22 @@ import org.apache.asterix.common.api.IExtension;
 import org.apache.asterix.common.cluster.IGlobalRecoveryManager;
 import org.apache.asterix.common.config.AsterixExtension;
 import org.apache.asterix.common.context.IStorageComponentProvider;
+import org.apache.asterix.common.dataflow.ICcApplicationContext;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.exceptions.RuntimeDataException;
 import org.apache.asterix.compiler.provider.AqlCompilationProvider;
 import org.apache.asterix.compiler.provider.ILangCompilationProvider;
 import org.apache.asterix.compiler.provider.SqlppCompilationProvider;
 import org.apache.asterix.hyracks.bootstrap.GlobalRecoveryManager;
-import org.apache.asterix.om.functions.IFunctionExtensionManager;
+import org.apache.asterix.metadata.api.ICCExtensionManager;
+import org.apache.asterix.metadata.api.IMetadataExtension;
 import org.apache.asterix.om.functions.IFunctionManager;
 import org.apache.asterix.runtime.functions.FunctionCollection;
 import org.apache.asterix.runtime.functions.FunctionManager;
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.utils.ExtensionUtil;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
 import org.apache.hyracks.api.application.ICCServiceContext;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
@@ -52,14 +56,15 @@ import org.apache.hyracks.api.exceptions.HyracksDataException;
  * AsterixDB's implementation of {@code IAlgebraExtensionManager} and {@code IFunctionExtensionManager}
  * which takes care of initializing extensions for App and Compilation purposes
  */
-public class CCExtensionManager implements IFunctionExtensionManager {
+public class CCExtensionManager implements ICCExtensionManager {
 
     private final IStatementExecutorExtension statementExecutorExtension;
     private final ILangCompilationProvider aqlCompilationProvider;
     private final ILangCompilationProvider sqlppCompilationProvider;
     private final IFunctionManager functionManager;
     private final IGlobalRecoveryExtension globalRecoveryExtension;
-    private transient IStatementExecutorFactory statementExecutorFactory;
+    private final Function<ICcApplicationContext, IMetadataProvider<?, ?>> metadataProviderFactory;
+    private IStatementExecutorFactory statementExecutorFactory;
 
     /**
      * Initialize {@link org.apache.asterix.app.cc.CCExtensionManager} from configuration
@@ -78,6 +83,7 @@ public class CCExtensionManager implements IFunctionExtensionManager {
         Pair<ExtensionId, IFunctionManager> fm = null;
         IStatementExecutorExtension see = null;
         IGlobalRecoveryExtension gre = null;
+        IMetadataExtension mpfe = null;
         if (list != null) {
             Set<ExtensionId> extensionIds = new HashSet<>();
             for (AsterixExtension extensionConf : list) {
@@ -99,7 +105,9 @@ public class CCExtensionManager implements IFunctionExtensionManager {
                     case RECOVERY:
                         gre = (IGlobalRecoveryExtension) extension;
                         break;
-                    default:
+                    case METADATA:
+                        IMetadataExtension mde = (IMetadataExtension) extension;
+                        mpfe = ExtensionUtil.extendMetadataProviderFactory(mpfe, mde);
                         break;
                 }
             }
@@ -110,6 +118,7 @@ public class CCExtensionManager implements IFunctionExtensionManager {
         this.functionManager =
                 fm == null ? new FunctionManager(FunctionCollection.createDefaultFunctionCollection()) : fm.second;
         this.globalRecoveryExtension = gre;
+        this.metadataProviderFactory = mpfe != null ? mpfe.getMetadataProviderFactory() : null;
     }
 
     /** @deprecated use getStatementExecutorFactory instead */
@@ -150,4 +159,9 @@ public class CCExtensionManager implements IFunctionExtensionManager {
     public IFunctionManager getFunctionManager() {
         return functionManager;
     }
-}
+
+    @Override
+    public Function<ICcApplicationContext, IMetadataProvider<?, ?>> getMetadataProviderFactory() {
+        return metadataProviderFactory;
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 6351d0d..64a9a63 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -298,7 +298,7 @@ public class QueryTranslator extends AbstractLangTranslator implements IStatemen
                     sessionOutput.out().println(ApiServlet.HTML_STATEMENT_SEPARATOR);
                 }
                 validateOperation(appCtx, activeDataverse, stmt);
-                MetadataProvider metadataProvider = new MetadataProvider(appCtx, activeDataverse);
+                MetadataProvider metadataProvider = MetadataProvider.create(appCtx, activeDataverse);
                 metadataProvider.getConfig().putAll(config);
                 metadataProvider.setWriterFactory(writerFactory);
                 metadataProvider.setResultSerializerFactoryProvider(resultSerializerFactoryProvider);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/GlobalRecoveryManager.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/GlobalRecoveryManager.java
index 5fc5c57..8165316 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/GlobalRecoveryManager.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/GlobalRecoveryManager.java
@@ -143,7 +143,7 @@ public class GlobalRecoveryManager implements IGlobalRecoveryManager {
     private MetadataTransactionContext recoverDatasets(ICcApplicationContext appCtx,
             MetadataTransactionContext mdTxnCtx, Dataverse dataverse) throws Exception {
         if (!dataverse.getDataverseName().equals(MetadataConstants.METADATA_DATAVERSE_NAME)) {
-            MetadataProvider metadataProvider = new MetadataProvider(appCtx, dataverse);
+            MetadataProvider metadataProvider = MetadataProvider.create(appCtx, dataverse);
             try {
                 List<Dataset> datasets =
                         MetadataManager.INSTANCE.getDataverseDatasets(mdTxnCtx, dataverse.getDataverseName());
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/utils/ExtensionUtil.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/utils/ExtensionUtil.java
index 380c5a9..c3d1ac5 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/utils/ExtensionUtil.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/utils/ExtensionUtil.java
@@ -103,7 +103,7 @@ public class ExtensionUtil {
     }
 
     /**
-     * Validate no extension conflict and extends tuple translator provider
+     * Validates no extension conflict and extends tuple translator provider
      *
      * @param metadataExtension
      *            place holder for tuple translator provider extension
@@ -121,4 +121,24 @@ public class ExtensionUtil {
         }
         return mde.getMetadataTupleTranslatorProvider() == null ? null : mde;
     }
+
+    /**
+     * Validates no extension conflict and extends metadata provider factory
+     *
+     * @param metadataExtension
+     *            place holder for metadata provider factory
+     * @param mde
+     *            user defined metadata extension
+     * @return the metadata extension if the extension defines a metadata provider factory, null otherwise
+     * @throws RuntimeDataException
+     *             if an extension conflict was detected
+     */
+    public static IMetadataExtension extendMetadataProviderFactory(IMetadataExtension metadataExtension,
+            IMetadataExtension mde) throws RuntimeDataException {
+        if (metadataExtension != null) {
+            throw new RuntimeDataException(ErrorCode.EXTENSION_COMPONENT_CONFLICT, metadataExtension.getId(),
+                    mde.getId(), IMetadataExtension.class.getSimpleName());
+        }
+        return mde.getMetadataProviderFactory() == null ? null : mde;
+    }
 }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/servlet/ConnectorApiServletTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/servlet/ConnectorApiServletTest.java
index 5c6f0b6..a4ffab5 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/servlet/ConnectorApiServletTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/api/http/servlet/ConnectorApiServletTest.java
@@ -181,8 +181,8 @@ public class ConnectorApiServletTest {
     private ARecordType getMetadataRecordType(DataverseName dataverseName, String datasetName) throws Exception {
         MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         // Retrieves file splits of the dataset.
-        MetadataProvider metadataProvider = new MetadataProvider(
-                (ICcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext(), null);
+        MetadataProvider metadataProvider = MetadataProvider
+                .create((ICcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext(), null);
         try {
             metadataProvider.setMetadataTxnContext(mdTxnCtx);
             Dataset dataset = metadataProvider.findDataset(dataverseName, datasetName);
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
index ccbf5ec..9f37c9b 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/bootstrap/TestNodeController.java
@@ -223,7 +223,7 @@ public class TestNodeController {
             throws AlgebricksException, HyracksDataException, RemoteException, ACIDException {
         CcApplicationContext appCtx =
                 (CcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext();
-        MetadataProvider mdProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider mdProvider = MetadataProvider.create(appCtx, null);
         try {
             MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
             org.apache.hyracks.algebricks.common.utils.Pair<ILSMMergePolicyFactory, Map<String, String>> mergePolicy =
@@ -330,7 +330,7 @@ public class TestNodeController {
             throws AlgebricksException, HyracksDataException, RemoteException, ACIDException {
         CcApplicationContext appCtx =
                 (CcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext();
-        MetadataProvider mdProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider mdProvider = MetadataProvider.create(appCtx, null);
         try {
             MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
             org.apache.hyracks.algebricks.common.utils.Pair<ILSMMergePolicyFactory, Map<String, String>> mergePolicy =
@@ -458,7 +458,7 @@ public class TestNodeController {
         Index index = primaryIndexInfo.getIndex();
         CcApplicationContext appCtx =
                 (CcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext();
-        MetadataProvider mdProvider = new MetadataProvider(appCtx, dataverse);
+        MetadataProvider mdProvider = MetadataProvider.create(appCtx, dataverse);
         try {
             return dataset.getResourceFactory(mdProvider, index, primaryIndexInfo.recordType, primaryIndexInfo.metaType,
                     primaryIndexInfo.mergePolicyFactory, primaryIndexInfo.mergePolicyProperties);
@@ -479,7 +479,7 @@ public class TestNodeController {
                 mergePolicy.first, mergePolicy.second, filterFields, primaryKeyIndexes, primaryKeyIndicators);
         Dataverse dataverse = new Dataverse(dataset.getDataverseName(), NonTaggedDataFormat.class.getName(),
                 MetadataUtil.PENDING_NO_OP);
-        MetadataProvider mdProvider = new MetadataProvider(
+        MetadataProvider mdProvider = MetadataProvider.create(
                 (ICcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext(), dataverse);
         try {
             IResourceFactory resourceFactory = dataset.getResourceFactory(mdProvider, primaryIndexInfo.index,
@@ -505,7 +505,7 @@ public class TestNodeController {
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
         Dataverse dataverse = new Dataverse(primaryIndexInfo.dataset.getDataverseName(),
                 NonTaggedDataFormat.class.getName(), MetadataUtil.PENDING_NO_OP);
-        MetadataProvider mdProvider = new MetadataProvider(
+        MetadataProvider mdProvider = MetadataProvider.create(
                 (ICcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext(), dataverse);
         SecondaryIndexInfo secondaryIndexInfo = new SecondaryIndexInfo(primaryIndexInfo, secondaryIndex);
         try {
@@ -780,7 +780,7 @@ public class TestNodeController {
             int[] keyIndexes, List<Integer> keyIndicators, StorageComponentProvider storageComponentProvider,
             IFrameOperationCallbackFactory frameOpCallbackFactory, boolean hasSecondaries) throws Exception {
         MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
-        MetadataProvider mdProvider = new MetadataProvider(
+        MetadataProvider mdProvider = MetadataProvider.create(
                 (ICcApplicationContext) ExecutionTestUtil.integrationUtil.cc.getApplicationContext(),
                 MetadataBuiltinEntities.DEFAULT_DATAVERSE);
         org.apache.hyracks.algebricks.common.utils.Pair<ILSMMergePolicyFactory, Map<String, String>> mergePolicy =
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/common/TestDataUtil.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/common/TestDataUtil.java
index b184053..dfe696f 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/common/TestDataUtil.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/common/TestDataUtil.java
@@ -158,7 +158,7 @@ public class TestDataUtil {
             String datasetName, String[] targetNodes) throws Exception {
         ICcApplicationContext ccAppCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        MetadataProvider metadataProvider = new MetadataProvider(ccAppCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(ccAppCtx, null);
         try {
             ActiveNotificationHandler activeNotificationHandler =
                     (ActiveNotificationHandler) ccAppCtx.getActiveNotificationHandler();
@@ -190,7 +190,7 @@ public class TestDataUtil {
             throws AlgebricksException, RemoteException {
         final ICcApplicationContext appCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        final MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        final MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
         Dataset dataset;
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveEventsListenerTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveEventsListenerTest.java
index bc38254..21b364f 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveEventsListenerTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveEventsListenerTest.java
@@ -43,12 +43,12 @@ import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.external.feed.watch.WaitForStateSubscriber;
 import org.apache.asterix.file.StorageComponentProvider;
+import org.apache.asterix.metadata.api.ICCExtensionManager;
 import org.apache.asterix.metadata.bootstrap.MetadataBuiltinEntities;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.Feed;
 import org.apache.asterix.metadata.lock.MetadataLockManager;
-import org.apache.asterix.om.functions.IFunctionExtensionManager;
 import org.apache.asterix.runtime.functions.FunctionCollection;
 import org.apache.asterix.runtime.functions.FunctionManager;
 import org.apache.asterix.runtime.utils.CcApplicationContext;
@@ -89,7 +89,7 @@ public class ActiveEventsListenerTest {
     static CcApplicationContext appCtx;
     static IStatementExecutor statementExecutor;
     static IHyracksClientConnection hcc;
-    static IFunctionExtensionManager functionExtensionManager;
+    static ICCExtensionManager ccExtensionManager;
     static MetadataProvider metadataProvider;
     static IStorageComponentProvider componentProvider;
     static JobIdFactory jobIdFactory;
@@ -130,11 +130,11 @@ public class ActiveEventsListenerTest {
         Mockito.when(ccServiceCtx.getControllerService()).thenReturn(ccService);
         Mockito.when(ccService.getExecutor()).thenReturn(executor);
         locations = new AlgebricksAbsolutePartitionConstraint(nodes);
-        functionExtensionManager = Mockito.mock(IFunctionExtensionManager.class);
-        Mockito.when(functionExtensionManager.getFunctionManager())
+        ccExtensionManager = Mockito.mock(ICCExtensionManager.class);
+        Mockito.when(ccExtensionManager.getFunctionManager())
                 .thenReturn(new FunctionManager(FunctionCollection.createDefaultFunctionCollection()));
-        Mockito.when(appCtx.getExtensionManager()).thenReturn(functionExtensionManager);
-        metadataProvider = new MetadataProvider(appCtx, null);
+        Mockito.when(appCtx.getExtensionManager()).thenReturn(ccExtensionManager);
+        metadataProvider = MetadataProvider.create(appCtx, null);
         clusterController = new TestClusterControllerActor("CC", handler, allDatasets);
         nodeControllers = new TestNodeControllerActor[2];
         nodeControllers[0] = new TestNodeControllerActor(nodes[0], clusterController);
@@ -149,7 +149,7 @@ public class ActiveEventsListenerTest {
     }
 
     TestUserActor newUser(String name, CcApplicationContext appCtx) {
-        MetadataProvider actorMdProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider actorMdProvider = MetadataProvider.create(appCtx, null);
         return new TestUserActor("User: " + name, actorMdProvider, clusterController);
     }
 
@@ -1522,10 +1522,10 @@ public class ActiveEventsListenerTest {
             CcApplicationContext ccAppCtx = Mockito.mock(CcApplicationContext.class);
             IStatementExecutor statementExecutor = Mockito.mock(IStatementExecutor.class);
             IHyracksClientConnection hcc = Mockito.mock(IHyracksClientConnection.class);
-            IFunctionExtensionManager functionExtensionManager = Mockito.mock(IFunctionExtensionManager.class);
-            Mockito.when(functionExtensionManager.getFunctionManager())
+            ICCExtensionManager ccExtensionManager = Mockito.mock(ICCExtensionManager.class);
+            Mockito.when(ccExtensionManager.getFunctionManager())
                     .thenReturn(new FunctionManager(FunctionCollection.createDefaultFunctionCollection()));
-            Mockito.when(ccAppCtx.getExtensionManager()).thenReturn(functionExtensionManager);
+            Mockito.when(ccAppCtx.getExtensionManager()).thenReturn(ccExtensionManager);
             Mockito.when(ccAppCtx.getActiveNotificationHandler()).thenReturn(handler);
             Mockito.when(ccAppCtx.getMetadataLockManager()).thenReturn(lockManager);
             Mockito.when(ccAppCtx.getServiceContext()).thenReturn(ccServiceCtx);
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveStatsTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveStatsTest.java
index bc40304..963ba7c 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveStatsTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/active/ActiveStatsTest.java
@@ -103,7 +103,7 @@ public class ActiveStatsTest {
                 .create(appCtx, Collections.emptyList(), sessionOutput,
                         extensionManager.getCompilationProvider(Language.SQLPP), appCtx.getStorageComponentProvider(),
                         new ResponsePrinter(sessionOutput));
-        MetadataProvider mdProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider mdProvider = MetadataProvider.create(appCtx, null);
         // Add event listener
         ActiveEntityEventsListener eventsListener = new DummyFeedEventsListener(statementExecutor, appCtx, null,
                 entityId, datasetList, partitionConstraint, FeedIntakeOperatorNodePushable.class.getSimpleName(),
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/ddl/SecondaryBTreeOperationsHelperTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/ddl/SecondaryBTreeOperationsHelperTest.java
index 726a1bf..5a78ea8 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/ddl/SecondaryBTreeOperationsHelperTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/ddl/SecondaryBTreeOperationsHelperTest.java
@@ -63,7 +63,7 @@ public class SecondaryBTreeOperationsHelperTest {
     public void createPrimaryIndex() throws Exception {
         ICcApplicationContext appCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        final MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        final MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         MetadataTransactionContext mdTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxn);
         try {
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/metadata/MetadataTxnTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/metadata/MetadataTxnTest.java
index a758511..d4cb04f 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/metadata/MetadataTxnTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/metadata/MetadataTxnTest.java
@@ -77,7 +77,7 @@ public class MetadataTxnTest {
     public void abortMetadataTxn() throws Exception {
         ICcApplicationContext appCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        final MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        final MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext mdTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxn);
         final String nodeGroupName = "ng";
@@ -116,7 +116,7 @@ public class MetadataTxnTest {
                 "CREATE DATASET " + datasetName + "(KeyType) PRIMARY KEY id on " + nodeGroup + ";", format);
         // find source dataset
         Dataset sourceDataset;
-        MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
         try {
@@ -127,7 +127,7 @@ public class MetadataTxnTest {
         }
 
         // create rebalance metadata provider and metadata txn
-        metadataProvider = new MetadataProvider(appCtx, null);
+        metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext rebalanceTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(rebalanceTxn);
         try {
@@ -165,7 +165,7 @@ public class MetadataTxnTest {
         // get created dataset
         ICcApplicationContext appCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
         Dataset sourceDataset;
@@ -224,7 +224,7 @@ public class MetadataTxnTest {
     public void surviveInterruptOnMetadataTxnCommit() throws Exception {
         ICcApplicationContext appCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        final MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        final MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext mdTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxn);
         final String nodeGroupName = "ng";
@@ -256,7 +256,7 @@ public class MetadataTxnTest {
     public void failedFlushOnUncommittedMetadataTxn() throws Exception {
         ICcApplicationContext ccAppCtx =
                 (ICcApplicationContext) integrationUtil.getClusterControllerService().getApplicationContext();
-        final MetadataProvider metadataProvider = new MetadataProvider(ccAppCtx, null);
+        final MetadataProvider metadataProvider = MetadataProvider.create(ccAppCtx, null);
         final MetadataTransactionContext mdTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxn);
         final String nodeGroupName = "ng";
@@ -322,7 +322,7 @@ public class MetadataTxnTest {
         Dataset dataset = new Dataset(source.getDataverseName(), "ds_" + datasetPostfix, source.getDataverseName(),
                 source.getDatasetType().name(), source.getNodeGroupName(), NoMergePolicyFactory.NAME, null,
                 source.getDatasetDetails(), source.getHints(), DatasetConfig.DatasetType.INTERNAL, datasetPostfix, 0);
-        MetadataProvider metadataProvider = new MetadataProvider(appCtx, null);
+        MetadataProvider metadataProvider = MetadataProvider.create(appCtx, null);
         final MetadataTransactionContext writeTxn = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(writeTxn);
         try {
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/storage/IndexDropOperatorNodePushableTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/storage/IndexDropOperatorNodePushableTest.java
index aea32f5..f7f7207 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/storage/IndexDropOperatorNodePushableTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/storage/IndexDropOperatorNodePushableTest.java
@@ -147,7 +147,7 @@ public class IndexDropOperatorNodePushableTest {
             final MetadataTransactionContext mdTxn = MetadataManager.INSTANCE.beginTransaction();
             ICcApplicationContext appCtx = (ICcApplicationContext) ExecutionTestUtil.integrationUtil
                     .getClusterControllerService().getApplicationContext();
-            MetadataProvider metadataProver = new MetadataProvider(appCtx, null);
+            MetadataProvider metadataProver = MetadataProvider.create(appCtx, null);
             metadataProver.setMetadataTxnContext(mdTxn);
             final DataverseName defaultDv = MetadataBuiltinEntities.DEFAULT_DATAVERSE.getDataverseName();
             final Dataset dataset = MetadataManager.INSTANCE.getDataset(mdTxn, defaultDv, datasetName);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/async-deferred/AsyncDeferredQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/async-deferred/AsyncDeferredQueries.xml
index ff75720..47eb439 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/async-deferred/AsyncDeferredQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/async-deferred/AsyncDeferredQueries.xml
@@ -19,40 +19,40 @@
 <test-group name="async-deferred">
     <test-case FilePath="async-deferred">
         <compilation-unit name="async-failed">
-            <output-dir compare="Text">async-failed</output-dir>
+            <output-dir compare="Clean-JSON">async-failed</output-dir>
             <expected-error>Injected failure in inject-failure</expected-error>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="async-compilation-failed">
-            <output-dir compare="Text">async-compilation-failed</output-dir>
+            <output-dir compare="Clean-JSON">async-compilation-failed</output-dir>
             <expected-error>Cannot find dataset gargel</expected-error>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="deferred">
-            <output-dir compare="Text">deferred</output-dir>
+            <output-dir compare="Clean-JSON">deferred</output-dir>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="async">
-            <output-dir compare="Text">async</output-dir>
+            <output-dir compare="Clean-JSON">async</output-dir>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="async-repeated">
-            <output-dir compare="Text">async-repeated</output-dir>
+            <output-dir compare="Clean-JSON">async-repeated</output-dir>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="async-running">
-            <output-dir compare="Text">async-running</output-dir>
+            <output-dir compare="Clean-JSON">async-running</output-dir>
         </compilation-unit>
     </test-case>
     <test-case FilePath="async-deferred">
         <compilation-unit name="async-exhausted-result">
-            <output-dir compare="Text">async-exhausted-result</output-dir>
-            <expected-error>HYR0093</expected-error>
+            <output-dir compare="Clean-JSON">async-exhausted-result</output-dir>
+            <expected-error>Premature end of chunk</expected-error> <!--TODO:REVISIT -->
             <source-location>false</source-location>
         </compilation-unit>
     </test-case>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.json
deleted file mode 100644
index 09e86cc..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "i": 1, "i2": 1 }
-{ "i": 2, "i2": 4 }
-{ "i": 3, "i2": 9 }
-{ "i": 4, "i2": 16 }
-{ "i": 5, "i2": 25 }
-{ "i": 6, "i2": 36 }
-{ "i": 7, "i2": 49 }
-{ "i": 8, "i2": 64 }
-{ "i": 9, "i2": 81 }
-{ "i": 10, "i2": 100 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.regexjson
new file mode 100644
index 0000000..df5177b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-exhausted-result/async-exhausted-result.3.regexjson
@@ -0,0 +1,15 @@
+{
+	"results": [
+	  { "i": 1, "i2": 1 },
+	  { "i": 2, "i2": 4 },
+	  { "i": 3, "i2": 9 },
+	  { "i": 4, "i2": 16 },
+	  { "i": 5, "i2": 25 },
+	  { "i": 6, "i2": 36 },
+	  { "i": 7, "i2": 49 },
+	  { "i": 8, "i2": 64 },
+	  { "i": 9, "i2": 81 },
+	  { "i": 10, "i2": 100 }
+  ],
+  "metrics": "R{.*}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.json
deleted file mode 100644
index 09e86cc..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "i": 1, "i2": 1 }
-{ "i": 2, "i2": 4 }
-{ "i": 3, "i2": 9 }
-{ "i": 4, "i2": 16 }
-{ "i": 5, "i2": 25 }
-{ "i": 6, "i2": 36 }
-{ "i": 7, "i2": 49 }
-{ "i": 8, "i2": 64 }
-{ "i": 9, "i2": 81 }
-{ "i": 10, "i2": 100 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.regexjson
new file mode 100644
index 0000000..df5177b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.3.regexjson
@@ -0,0 +1,15 @@
+{
+	"results": [
+	  { "i": 1, "i2": 1 },
+	  { "i": 2, "i2": 4 },
+	  { "i": 3, "i2": 9 },
+	  { "i": 4, "i2": 16 },
+	  { "i": 5, "i2": 25 },
+	  { "i": 6, "i2": 36 },
+	  { "i": 7, "i2": 49 },
+	  { "i": 8, "i2": 64 },
+	  { "i": 9, "i2": 81 },
+	  { "i": 10, "i2": 100 }
+  ],
+  "metrics": "R{.*}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.json
deleted file mode 100644
index 09e86cc..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "i": 1, "i2": 1 }
-{ "i": 2, "i2": 4 }
-{ "i": 3, "i2": 9 }
-{ "i": 4, "i2": 16 }
-{ "i": 5, "i2": 25 }
-{ "i": 6, "i2": 36 }
-{ "i": 7, "i2": 49 }
-{ "i": 8, "i2": 64 }
-{ "i": 9, "i2": 81 }
-{ "i": 10, "i2": 100 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.regexjson
new file mode 100644
index 0000000..df5177b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-repeated/async-repeated.4.regexjson
@@ -0,0 +1,15 @@
+{
+	"results": [
+	  { "i": 1, "i2": 1 },
+	  { "i": 2, "i2": 4 },
+	  { "i": 3, "i2": 9 },
+	  { "i": 4, "i2": 16 },
+	  { "i": 5, "i2": 25 },
+	  { "i": 6, "i2": 36 },
+	  { "i": 7, "i2": 49 },
+	  { "i": 8, "i2": 64 },
+	  { "i": 9, "i2": 81 },
+	  { "i": 10, "i2": 100 }
+  ],
+  "metrics": "R{.*}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.json
deleted file mode 100644
index 859d906..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.json
+++ /dev/null
@@ -1 +0,0 @@
-"result"
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.regexjson
new file mode 100644
index 0000000..a2a5f0a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.4.regexjson
@@ -0,0 +1,6 @@
+{
+	"results": [
+	  "result"
+  ],
+  "metrics": "R{.*}"
+}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.json
deleted file mode 100644
index 09e86cc..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "i": 1, "i2": 1 }
-{ "i": 2, "i2": 4 }
-{ "i": 3, "i2": 9 }
-{ "i": 4, "i2": 16 }
-{ "i": 5, "i2": 25 }
-{ "i": 6, "i2": 36 }
-{ "i": 7, "i2": 49 }
-{ "i": 8, "i2": 64 }
-{ "i": 9, "i2": 81 }
-{ "i": 10, "i2": 100 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.regexjson
new file mode 100644
index 0000000..df5177b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.3.regexjson
@@ -0,0 +1,15 @@
+{
+	"results": [
+	  { "i": 1, "i2": 1 },
+	  { "i": 2, "i2": 4 },
+	  { "i": 3, "i2": 9 },
+	  { "i": 4, "i2": 16 },
+	  { "i": 5, "i2": 25 },
+	  { "i": 6, "i2": 36 },
+	  { "i": 7, "i2": 49 },
+	  { "i": 8, "i2": 64 },
+	  { "i": 9, "i2": 81 },
+	  { "i": 10, "i2": 100 }
+  ],
+  "metrics": "R{.*}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.json
deleted file mode 100644
index 09e86cc..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "i": 1, "i2": 1 }
-{ "i": 2, "i2": 4 }
-{ "i": 3, "i2": 9 }
-{ "i": 4, "i2": 16 }
-{ "i": 5, "i2": 25 }
-{ "i": 6, "i2": 36 }
-{ "i": 7, "i2": 49 }
-{ "i": 8, "i2": 64 }
-{ "i": 9, "i2": 81 }
-{ "i": 10, "i2": 100 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.regexjson
new file mode 100644
index 0000000..df5177b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/deferred/deferred.2.regexjson
@@ -0,0 +1,15 @@
+{
+	"results": [
+	  { "i": 1, "i2": 1 },
+	  { "i": 2, "i2": 4 },
+	  { "i": 3, "i2": 9 },
+	  { "i": 4, "i2": 16 },
+	  { "i": 5, "i2": 25 },
+	  { "i": 6, "i2": 36 },
+	  { "i": 7, "i2": 49 },
+	  { "i": 8, "i2": 64 },
+	  { "i": 9, "i2": 81 },
+	  { "i": 10, "i2": 100 }
+  ],
+  "metrics": "R{.*}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/ICCExtensionManager.java
similarity index 60%
copy from asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java
copy to asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/ICCExtensionManager.java
index 62c4273..072dfce 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/ICCExtensionManager.java
@@ -16,15 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.asterix.metadata.api;
 
-import org.apache.asterix.metadata.entitytupletranslators.MetadataTupleTranslatorProvider;
+import java.util.function.Function;
 
-@FunctionalInterface
-public interface INCExtensionManager {
+import org.apache.asterix.common.dataflow.ICcApplicationContext;
+import org.apache.asterix.om.functions.IFunctionExtensionManager;
+import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
 
+public interface ICCExtensionManager extends IFunctionExtensionManager {
     /**
-     * @return the metadata tuple translator provider
+     * Returns a factory for {@link org.apache.asterix.metadata.declared.MetadataProvider}
+     * or {@code null} if the default implementation should be used.
      */
-    MetadataTupleTranslatorProvider getMetadataTupleTranslatorProvider();
-}
+    Function<ICcApplicationContext, IMetadataProvider<?, ?>> getMetadataProviderFactory();
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataExtension.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataExtension.java
index ef624ed..fe7e441 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataExtension.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataExtension.java
@@ -21,10 +21,13 @@ package org.apache.asterix.metadata.api;
 
 import java.rmi.RemoteException;
 import java.util.List;
+import java.util.function.Function;
 
 import org.apache.asterix.common.api.IExtension;
+import org.apache.asterix.common.dataflow.ICcApplicationContext;
 import org.apache.asterix.common.exceptions.ACIDException;
 import org.apache.asterix.metadata.entitytupletranslators.MetadataTupleTranslatorProvider;
+import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
 import org.apache.hyracks.api.application.INCServiceContext;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 
@@ -59,4 +62,11 @@ public interface IMetadataExtension extends IExtension {
      */
     void initializeMetadata(INCServiceContext ncServiceCtx) throws HyracksDataException, RemoteException, ACIDException;
 
+    /**
+     * Returns a factory for {@link org.apache.asterix.metadata.declared.MetadataProvider},
+     * or {@code null} if this extension doesn't define this factory.
+     */
+    default Function<ICcApplicationContext, IMetadataProvider<?, ?>> getMetadataProviderFactory() {
+        return null;
+    }
 }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java
index 62c4273..18b2ac6 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/INCExtensionManager.java
@@ -20,9 +20,7 @@ package org.apache.asterix.metadata.api;
 
 import org.apache.asterix.metadata.entitytupletranslators.MetadataTupleTranslatorProvider;
 
-@FunctionalInterface
 public interface INCExtensionManager {
-
     /**
      * @return the metadata tuple translator provider
      */
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
index 6f54a0c..5eff20b 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
@@ -29,6 +29,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 
 import org.apache.asterix.common.cluster.IClusterStateManager;
 import org.apache.asterix.common.config.DatasetConfig.DatasetType;
@@ -69,6 +70,7 @@ import org.apache.asterix.formats.nontagged.LinearizeComparatorFactoryProvider;
 import org.apache.asterix.formats.nontagged.TypeTraitProvider;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.metadata.MetadataTransactionContext;
+import org.apache.asterix.metadata.api.ICCExtensionManager;
 import org.apache.asterix.metadata.bootstrap.MetadataBuiltinEntities;
 import org.apache.asterix.metadata.dataset.hints.DatasetHints.DatasetCardinalityHint;
 import org.apache.asterix.metadata.entities.Dataset;
@@ -173,9 +175,16 @@ public class MetadataProvider implements IMetadataProvider<DataSourceId, String>
     private Map<String, Integer> externalDataLocks;
     private boolean blockingOperatorDisabled = false;
 
-    public MetadataProvider(ICcApplicationContext appCtx, Dataverse defaultDataverse) {
+    public static MetadataProvider create(ICcApplicationContext appCtx, Dataverse defaultDataverse) {
+        Function<ICcApplicationContext, IMetadataProvider<?, ?>> factory =
+                ((ICCExtensionManager) appCtx.getExtensionManager()).getMetadataProviderFactory();
+        MetadataProvider mp = factory != null ? (MetadataProvider) factory.apply(appCtx) : new MetadataProvider(appCtx);
+        mp.setDefaultDataverse(defaultDataverse);
+        return mp;
+    }
+
+    protected MetadataProvider(ICcApplicationContext appCtx) {
         this.appCtx = appCtx;
-        setDefaultDataverse(defaultDataverse);
         this.storageComponentProvider = appCtx.getStorageComponentProvider();
         storageProperties = appCtx.getStorageProperties();
         functionManager = ((IFunctionExtensionManager) appCtx.getExtensionManager()).getFunctionManager();
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/utils/AdmNodeUtils.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/utils/AdmNodeUtils.java
index 03ec540..f24bce0 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/utils/AdmNodeUtils.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/utils/AdmNodeUtils.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
 
 import org.apache.asterix.object.base.AdmArrayNode;
 import org.apache.asterix.object.base.AdmBigIntNode;
@@ -250,4 +251,16 @@ public class AdmNodeUtils {
         pointable.set(listPointable.getByteArray(), offset, len);
         return getAsAdmNode(pointable);
     }
+
+    public static String toString(AdmArrayNode nodeList, Function<IAdmNode, String> nodeToStringFunction,
+            String separator) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0, ln = nodeList.size(); i < ln; i++) {
+            if (i > 0) {
+                sb.append(separator);
+            }
+            sb.append(nodeToStringFunction.apply(nodeList.get(i)));
+        }
+        return sb.toString();
+    }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/NetworkUtil.java b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/NetworkUtil.java
index 763319f..228fd91 100644
--- a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/NetworkUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/NetworkUtil.java
@@ -27,6 +27,7 @@ import java.net.URISyntaxException;
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import javax.net.ssl.SSLEngine;
@@ -67,6 +68,17 @@ public class NetworkUtil {
         return builderFrom(host).setPath(path).build();
     }
 
+    public static URI appendUriPath(URI uri, String... pathSegments) throws URISyntaxException {
+        URIBuilder builder = new URIBuilder(uri);
+        List<String> path = builder.getPathSegments();
+        if (path.isEmpty()) {
+            path = new ArrayList<>(pathSegments.length);
+        }
+        Collections.addAll(path, pathSegments);
+        builder.setPathSegments(path);
+        return builder.build();
+    }
+
     public static URIBuilder builderFrom(HttpHost host) {
         return new URIBuilder().setHost(host.getHostName()).setPort(host.getPort()).setScheme(host.getSchemeName());
     }